So konvertieren Sie ein Objekt in ein Byte-Array in C #


99

Ich habe eine Sammlung von Objekten, die ich in eine Binärdatei schreiben muss.

Die Bytes in der Datei müssen kompakt sein, daher kann ich sie nicht verwenden BinaryFormatter. BinaryFormatterwirft alle möglichen Informationen für Deserialisierungsbedürfnisse ein.

Wenn ich es versuche

byte[] myBytes = (byte[]) myObject 

Ich bekomme eine Laufzeitausnahme.

Ich brauche das, um schnell zu sein, also würde ich lieber keine Arrays von Bytes herumkopieren. Ich möchte nur, dass die Besetzung byte[] myBytes = (byte[]) myObjectfunktioniert!

OK, nur um klar zu sein, ich kann keine Metadaten in der Ausgabedatei haben. Nur die Objektbytes. Objekt zu Objekt gepackt. Basierend auf den erhaltenen Antworten sieht es so aus, als würde ich Buffer.BlockCopyCode auf niedriger Ebene schreiben . Möglicherweise mit unsicherem Code.

Antworten:


173

So konvertieren Sie ein Objekt in ein Byte-Array:

// Convert an object to a byte array
public static byte[] ObjectToByteArray(Object obj)
{
    BinaryFormatter bf = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        bf.Serialize(ms, obj);
        return ms.ToArray();
    }
}

Sie müssen diese Funktion nur in Ihren Code kopieren und das Objekt senden, das Sie in ein Byte-Array konvertieren müssen. Wenn Sie das Byte-Array erneut in ein Objekt konvertieren müssen, können Sie die folgende Funktion verwenden:

// Convert a byte array to an Object
public static Object ByteArrayToObject(byte[] arrBytes)
{
    using (var memStream = new MemoryStream())
    {
        var binForm = new BinaryFormatter();
        memStream.Write(arrBytes, 0, arrBytes.Length);
        memStream.Seek(0, SeekOrigin.Begin);
        var obj = binForm.Deserialize(memStream);
        return obj;
    }
}

Sie können diese Funktionen mit benutzerdefinierten Klassen verwenden. Sie müssen nur das [Serializable]Attribut in Ihrer Klasse hinzufügen , um die Serialisierung zu aktivieren


9
Ich habe es versucht und es wurden alle möglichen Metadaten hinzugefügt. Das OP sagte, er wolle keine Metadaten.
user316117

4
Ganz zu schweigen davon, dass jeder davon ausgeht, dass das, was Sie zu serialisieren versuchen, etwas ist, das Sie geschrieben haben oder das bereits für die Serialisierung eingerichtet wurde.
Hexum064

3
Sie können das Byte-Array direkt an den Konstruktor von MemoryStreamim zweiten Codebeispiel übergeben. Dies würde die Verwendung von Write(...)und eliminieren Seek(...).
unbekannt6656

41

Wenn die serialisierten Daten sehr kompakt sein sollen, können Sie selbst Serialisierungsmethoden schreiben. Auf diese Weise haben Sie ein Minimum an Overhead.

Beispiel:

public class MyClass {

   public int Id { get; set; }
   public string Name { get; set; }

   public byte[] Serialize() {
      using (MemoryStream m = new MemoryStream()) {
         using (BinaryWriter writer = new BinaryWriter(m)) {
            writer.Write(Id);
            writer.Write(Name);
         }
         return m.ToArray();
      }
   }

   public static MyClass Desserialize(byte[] data) {
      MyClass result = new MyClass();
      using (MemoryStream m = new MemoryStream(data)) {
         using (BinaryReader reader = new BinaryReader(m)) {
            result.Id = reader.ReadInt32();
            result.Name = reader.ReadString();
         }
      }
      return result;
   }

}

Was ist, ich habe mehrere Ints zu schreiben und mehrere Zeichenfolge?
Smith

1
@ Smith: Ja, das kannst du, schreib sie einfach nacheinander. Sie BinaryWriterschreiben sie in einem Format, das BinaryReadersie lesen können, solange Sie sie in derselben Reihenfolge schreiben und lesen.
Guffa

1
Was ist der Unterschied zwischen BinaryWriter/Readerund mit einemBinaryFormatter
Smith

3
@Smith: Mit BinaryWriter/ReaderIhnen führen Sie die Serialisierung / Deserialisierung selbst durch und können nur die Daten schreiben / lesen, die unbedingt benötigt werden, so kompakt wie möglich. Der BinaryFormatterverwendet Reflection, um herauszufinden, welche Daten geschrieben / gelesen werden sollen, und verwendet ein Format, das für alle möglichen Fälle funktioniert. Es enthält auch die Metainformationen zum Format im Stream, sodass noch mehr Overhead entsteht.
Guffa

1
@Smith: Sie können die Aufzählung in int(oder wenn Sie einen anderen Typ als Speicher für die Aufzählung angegeben haben) umwandeln und schreiben. Wenn Sie es lesen, können Sie es in den Aufzählungstyp umwandeln.
Guffa

31

Nun, eine Besetzung von myObjectbis byte[]wird niemals funktionieren, es sei denn, Sie haben eine explizite Konvertierung oder wenn myObject es sich um eine handelt byte[]. Sie benötigen einen Serialisierung Rahmen von irgendeiner Art. Es gibt viele da draußen, einschließlich Protokollpuffer, die mir sehr am Herzen liegen. Es ist ziemlich "schlank und gemein" in Bezug auf Raum und Zeit.

Sie werden feststellen, dass fast alle Serialisierungs-Frameworks erhebliche Einschränkungen hinsichtlich der Serialisierung aufweisen - Protokollpuffer mehr als einige, da sie plattformübergreifend sind.

Wenn Sie mehr Anforderungen stellen können, können wir Ihnen mehr helfen - aber es wird nie so einfach wie das Casting ...

EDIT: Nur um darauf zu antworten:

Ich brauche meine Binärdatei, um die Bytes des Objekts zu enthalten. Nur die Bytes, überhaupt keine Metadaten. Objekt zu Objekt gepackt. Also werde ich eine benutzerdefinierte Serialisierung implementieren.

Bitte denken Sie daran, dass die Bytes in Ihren Objekten häufig Verweise sind. Sie müssen also herausfinden, was Sie damit tun sollen.

Ich vermute, Sie werden feststellen, dass das Entwerfen und Implementieren Ihres eigenen benutzerdefinierten Serialisierungsframeworks schwieriger ist, als Sie sich vorstellen.

Ich persönlich würde empfehlen, dass Sie sich nicht die Mühe machen, ein allgemeines Serialisierungsframework zu entwickeln, wenn Sie dies nur für einige bestimmte Typen tun müssen. Implementieren Sie einfach eine Instanzmethode und eine statische Methode in allen benötigten Typen:

public void WriteTo(Stream stream)
public static WhateverType ReadFrom(Stream stream)

Eines ist zu beachten: Alles wird schwieriger, wenn es um Vererbung geht. Wenn Sie ohne Vererbung wissen, mit welchem ​​Typ Sie beginnen, müssen Sie keine Typinformationen angeben. Natürlich gibt es auch die Frage der Versionierung - müssen Sie sich Gedanken über die Abwärts- und Vorwärtskompatibilität mit verschiedenen Versionen Ihrer Typen machen?


Ist es für mich richtiger, dies als "protobuf-csharp-port" (Google-Code) oder "dotnet-protobufs" (Git) zu bezeichnen?
Marc Gravell

1
Ich brauche meine Binärdatei, um die Bytes des Objekts zu enthalten. Nur die Bytes, überhaupt keine Metadaten. Objekt zu Objekt gepackt. Also werde ich eine benutzerdefinierte Serialisierung implementieren.
Chuckhlogan

6
Das Risiko von null Metadaten besteht darin, dass Sie dann sehr versionstolerant sind, da es nur sehr wenige Möglichkeiten gibt, Flexibilität zuzulassen, bevor es zu spät ist. Protokollpuffer sind ziemlich datenreich. Benötigen Sie wirklich diese zusätzliche Drehung der Schraube?
Marc Gravell

@Marc: Und natürlich kann PB für ganze Zahlen dichter sein als die rohen Bytes ...
Jon Skeet

16

Ich nahm die Antwort von Crystalonics und wandelte sie in Erweiterungsmethoden um. Ich hoffe, jemand anderes wird sie nützlich finden:

public static byte[] SerializeToByteArray(this object obj)
{
    if (obj == null)
    {
        return null;
    }
    var bf = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        bf.Serialize(ms, obj);
        return ms.ToArray();
    }
}

public static T Deserialize<T>(this byte[] byteArray) where T : class
{
    if (byteArray == null)
    {
        return null;
    }
    using (var memStream = new MemoryStream())
    {
        var binForm = new BinaryFormatter();
        memStream.Write(byteArray, 0, byteArray.Length);
        memStream.Seek(0, SeekOrigin.Begin);
        var obj = (T)binForm.Deserialize(memStream);
        return obj;
    }
}

1
Dieser ist wirklich nützlich und einfach! Danke dir.
MrHIDEn

13

Sie sprechen wirklich von Serialisierung, die viele Formen annehmen kann. Da Sie kleine und binäre Dateien wünschen, sind Protokollpuffer möglicherweise eine praktikable Option, die auch Versionsverträglichkeit und Portabilität bietet. Im Gegensatz dazu enthält BinaryFormatterdas Drahtformat des Protokollpuffers nicht alle Typmetadaten. nur sehr knappe Markierungen, um Daten zu identifizieren.

In .NET gibt es einige Implementierungen. bestimmtes

Ich würde demütig , dass protobuf-net argumentieren (was ich schrieb) ermöglicht mehr .NET-idiomatische Verwendung mit typischem C # -Klassen ( „normalen“ Protokoll-Puffer neigt dazu, die Nachfrage Code Generation); beispielsweise:

[ProtoContract]
public class Person {
   [ProtoMember(1)]
   public int Id {get;set;}
   [ProtoMember(2)]
   public string Name {get;set;}
}
....
Person person = new Person { Id = 123, Name = "abc" };
Serializer.Serialize(destStream, person);
...
Person anotherPerson = Serializer.Deserialize<Person>(sourceStream);

1
Selbst "knappe Marker" sind noch Metadaten. Mein Verständnis dessen, was das OP wollte, war nichts anderes als die Daten im Objekt. Wenn das Objekt beispielsweise eine Struktur mit 2 32-Bit-Ganzzahlen wäre, würde er erwarten, dass das Ergebnis ein Byte-Array von 8 Bytes ist.
user316117

@ user316117, was dann ein echtes Problem für die Versionierung ist. Jeder Ansatz hat Vor- und Nachteile.
Marc Gravell


Gibt es eine Möglichkeit, die Verwendung der Proto * -Attribute zu vermeiden? Die Entitäten, die ich verwenden möchte, befinden sich in einer Bibliothek eines Drittanbieters.
Alex 75

5

Das hat bei mir funktioniert:

byte[] bfoo = (byte[])foo;

foo ist ein Objekt, von dem ich zu 100% sicher bin, dass es sich um ein Byte-Array handelt.


2

Schauen Sie sich Serialization an , eine Technik zum "Konvertieren" eines gesamten Objekts in einen Bytestream. Sie können es an das Netzwerk senden oder in eine Datei schreiben und später wieder in einem Objekt wiederherstellen.


Ich denke, Chuckhlogan hat dies ausdrücklich abgelehnt (Formatter == Serialization).
Henk Holterman

@Henk - es kommt darauf an, was die Gründe sind; er erwähnte die zusätzlichen Informationen, die ich als Typ-Metadaten und Feldinformationen betrachte; Sie können die Serialisierung ohne diesen Aufwand verwenden. nur nicht mit BinaryFormatter.
Marc Gravell

2

Ich habe einen anderen Weg gefunden, ein Objekt in ein Byte [] zu konvertieren. Hier ist meine Lösung:

IEnumerable en = (IEnumerable) myObject;
byte[] myBytes = en.OfType<byte>().ToArray();

Grüße


1

Um direkt auf den Speicher eines Objekts zuzugreifen (um einen "Core Dump" durchzuführen), müssen Sie unsicheren Code eingeben.

Wenn Sie etwas Kompakteres als BinaryWriter oder einen Rohspeicherauszug wünschen, müssen Sie einen benutzerdefinierten Serialisierungscode schreiben, der die kritischen Informationen aus dem Objekt extrahiert und optimal packt.

PS bearbeiten Es ist sehr einfach, den BinaryWriter-Ansatz in einen DeflateStream zu packen, um die Daten zu komprimieren, wodurch sich die Datengröße normalerweise ungefähr halbiert.


1
Unsicherer Code reicht nicht aus. Mit C # und CLR können Sie auch in unsicherem Code keinen Rohzeiger auf ein verwaltetes Objekt setzen oder zwei Objektreferenzen in eine Union einfügen.
Pavel Minaev

0

Ich glaube, was Sie versuchen zu tun, ist unmöglich.

Der erzeugte Junk BinaryFormatterist erforderlich, um das Objekt aus der Datei wiederherzustellen, nachdem Ihr Programm gestoppt wurde.
Es ist jedoch möglich, die Objektdaten abzurufen. Sie müssen lediglich die genaue Größe kennen (schwieriger als es sich anhört):

public static unsafe byte[] Binarize(object obj, int size)
{
    var r = new byte[size];
    var rf = __makeref(obj);
    var a = **(IntPtr**)(&rf);
    Marshal.Copy(a, r, 0, size);
    return res;
}

Dies kann wiederhergestellt werden über:

public unsafe static dynamic ToObject(byte[] bytes)
{
    var rf = __makeref(bytes);
    **(int**)(&rf) += 8;
    return GCHandle.Alloc(bytes).Target;
}

Der Grund, warum die oben genannten Methoden für die Serialisierung nicht funktionieren, ist, dass die ersten vier Bytes in den zurückgegebenen Daten a entsprechen RuntimeTypeHandle. Das RuntimeTypeHandlebeschreibt das Layout / den Typ des Objekts, aber der Wert ändert sich jedes Mal, wenn das Programm ausgeführt wird.

BEARBEITEN: das ist dumm mach das nicht -> Wenn du den Typ des zu deserialisierenden Objekts sicher schon kennst, kannst du diese Bytes umschaltenBitConvertes.GetBytes((int)typeof(yourtype).TypeHandle.Value) Zeitpunkt der Deserialisierung .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.