Konvertieren Sie ein Objekt in ein Byte []

138

Ich schreibe einen Prototyp einer TCP-Verbindung und habe Probleme beim Homogenisieren der zu sendenden Daten.

Im Moment sende ich nur Zeichenfolgen, aber in Zukunft möchten wir in der Lage sein, jedes Objekt zu senden.

Der Code ist im Moment recht einfach, da ich dachte, alles könnte in ein Byte-Array umgewandelt werden:

void SendData(object headerObject, object bodyObject)
{
  byte[] header = (byte[])headerObject;  //strings at runtime, 
  byte[] body = (byte[])bodyObject;      //invalid cast exception

  // Unable to cast object of type 'System.String' to type 'System.Byte[]'.
  ...
}

Dies ist natürlich leicht genug mit einem zu lösen

if( state.headerObject is System.String ){...}

Das Problem ist, wenn ich es so mache, muss ich nach JEDEM Objekttyp suchen, der zur Laufzeit nicht in ein Byte [] umgewandelt werden kann.

Da ich nicht jedes Objekt kenne, das zur Laufzeit nicht in ein Byte [] umgewandelt werden kann, ist dies wirklich keine Option.

Wie konvertiert man ein Objekt überhaupt in ein Byte-Array in C # .NET 4.0?

Steve H.
quelle
2
Dies ist im Allgemeinen in keiner sinnvollen Weise möglich (betrachten Sie beispielsweise eine Instanz von FileStreamoder ein Objekt, das ein solches Handle kapselt).
Jason
2
Beabsichtigen Sie, dass alle Clients .NET ausführen? Wenn die Antwort Nein lautet, sollten Sie eine andere Form der Serialisierung in Betracht ziehen (XML, JSON oder ähnliches)
R. Martinho Fernandes

Antworten:

195

Verwenden Sie die BinaryFormatter:

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

Beachten Sie, dass objalle darin enthaltenen Eigenschaften / Felder obj(und so weiter für alle ihre Eigenschaften / Felder) mit dem SerializableAttribut versehen werden müssen, damit dies erfolgreich serialisiert werden kann.

Daniel DiPaolo
quelle
13
Seien Sie vorsichtig mit dem, was Sie mit "einem" Objekt auf der anderen Seite tun, da dies möglicherweise keinen Sinn mehr ergibt (z. B. wenn dieses Objekt ein Handle für eine Datei oder ähnliches wäre)
Rowland Shaw
1
Ja, es gelten normale Vorbehalte, aber es ist keine schlechte Idee, die Leute daran zu erinnern.
Daniel DiPaolo
24
Es kann eine gute Idee sein, die Verwendung des MemoryStream in einen usingBlock zu packen , da dadurch der verwendete interne Puffer eifrig freigegeben wird.
R. Martinho Fernandes
1
Ist diese Methode .NET gebunden? Kann ich eine C-Struktur mit StructLayoutAtrribute serialisieren und per Socket an einen C-Code senden und erwarten, dass der C-Code die Struktur versteht? Ich denke nicht?
Joe
103

Überprüfen Sie diesen Artikel: http://www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

Verwenden Sie den folgenden Code

// Convert an object to a byte array
private byte[] ObjectToByteArray(Object obj)
{
    if(obj == null)
        return null;

    BinaryFormatter bf = new BinaryFormatter();
    MemoryStream ms = new MemoryStream();
    bf.Serialize(ms, obj);

    return ms.ToArray();
}

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

    return obj;
}
kombsh
quelle
10
Wie in einem Kommentar zu dieser Antwort erwähnt , MemorySteamsollte der in einen usingBlock eingeschlossen werden.
Rookie1024
Gibt es etwas, das ich zusätzlich beachten muss? Ich habe es auf diese Weise implementiert und das Formatieren eines Objekts mit 3 öffentlichen int32-Mitgliedern führt zu einem 244 Byte langen ByteArray. Weiß ich nichts über C # -Syntax oder gibt es etwas, das ich wahrscheinlich vermissen würde?
Dhein
Entschuldigung, ich kann Ihr Problem nicht bekommen. Können Sie den Code posten?
Kombsh
@kombsh Ich versuche es in Kurzform: [Serializable] class GameConfiguration {public map_options_t enumMapIndex; public Int32 iPlayerAmount; private Int32 iGameID; } byte [] baPacket; GameConfiguration objGameConfClient = neue GameConfiguration (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); Jetzt enthält baPacket ungefähr 244 Bytes f Inhalt. Ich erwartete nur 12.
Dhein
1
@kombsh Sie können Einwegobjekte in Ihrem Beispiel explizit entsorgen.
Rudolf Dvoracek
29

Wie andere bereits gesagt haben, können Sie die binäre Serialisierung verwenden, diese kann jedoch zusätzliche Bytes erzeugen oder in Objekte mit nicht genau denselben Daten deserialisiert werden. Die Verwendung von Reflexion ist dagegen ziemlich kompliziert und sehr langsam. Es gibt eine andere Lösung, mit der Sie Ihre Objekte strikt in Bytes konvertieren können und umgekehrt: Marshalling:

var size = Marshal.SizeOf(your_object);
// Both managed and unmanaged buffers required.
var bytes = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
// Copy object byte-to-byte to unmanaged memory.
Marshal.StructureToPtr(your_object, ptr, false);
// Copy data from unmanaged memory to managed buffer.
Marshal.Copy(ptr, bytes, 0, size);
// Release unmanaged memory.
Marshal.FreeHGlobal(ptr);

Und um Bytes in Objekt umzuwandeln:

var bytes = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.Copy(bytes, 0, ptr, size);
var your_object = (YourType)Marshal.PtrToStructure(ptr, typeof(YourType));
Marshal.FreeHGlobal(ptr);

Es ist merklich langsamer und teilweise unsicher, diesen Ansatz für kleine Objekte und Strukturen zu verwenden, die Feld für Feld mit Ihrer eigenen Serialisierung verglichen werden (aufgrund des doppelten Kopierens von / in nicht verwalteten Speicher), aber es ist die einfachste Möglichkeit, Objekte strikt in Byte [] zu konvertieren, ohne die Serialisierung zu implementieren und ohne [Serializable] -Attribut.

Aberro
quelle
1
Warum denkst du StructureToPtr+ Copyist langsam? Wie kann es langsamer sein als die Serialisierung? Gibt es eine schnellere Lösung?
Anton Samsonov
Wenn Sie es für kleine Strukturen verwenden, die aus wenigen einfachen Typen bestehen, ja (was ziemlich häufig vorkommt), ist es aufgrund von Marshalling und Quad-Kopieren langsam (von Objekt zu Heap, von Heap zu Bytes, von Bytes zu Heap, von Heap zu beanstanden). Es könnte schneller sein, wenn IntPtr anstelle von Bytes verwendet wird, aber in diesem Fall nicht. Und für solche Typen ist es schneller, einen eigenen Serializer zu schreiben, der einfach Werte in das Byte-Array einfügt. Ich sage nicht, dass es langsamer ist als die eingebaute Serialisierung oder dass es "so verdammt langsam" ist.
Aberro
1
Ich mag diese Methode, da sie Byte für Byte abbildet. Dies ist eine wirklich gute Methode, um Speicher mit C ++ - Mapping auszutauschen. +1 für dich.
Hao Nguyen
2
Hinweis für potenzielle Benutzer: Diese Antwort ist zwar sehr intelligent, funktioniert jedoch nicht für Strukturarrays, Objekte, die nicht als nicht verwaltete Struktur gemarshallt werden können, oder Objekte, deren Hierarchie ein ComVisible (false) -Eltern enthält.
TernaryTopiary
1
Um zu deserilisieren, wie Sie die "Größe" erhalten haben? invar bytes = new byte[size];
Ricardo
13

Was Sie suchen, ist Serialisierung. Für die .Net-Plattform stehen verschiedene Formen der Serialisierung zur Verfügung

JaredPar
quelle
10
public static class SerializerDeserializerExtensions
{
    public static byte[] Serializer(this object _object)
    {   
        byte[] bytes;
        using (var _MemoryStream = new MemoryStream())
        {
            IFormatter _BinaryFormatter = new BinaryFormatter();
            _BinaryFormatter.Serialize(_MemoryStream, _object);
            bytes = _MemoryStream.ToArray();
        }
        return bytes;
    }

    public static T Deserializer<T>(this byte[] _byteArray)
    {   
        T ReturnValue;
        using (var _MemoryStream = new MemoryStream(_byteArray))
        {
            IFormatter _BinaryFormatter = new BinaryFormatter();
            ReturnValue = (T)_BinaryFormatter.Deserialize(_MemoryStream);    
        }
        return ReturnValue;
    }
}

Sie können es wie unten Code verwenden.

DataTable _DataTable = new DataTable();
_DataTable.Columns.Add(new DataColumn("Col1"));
_DataTable.Columns.Add(new DataColumn("Col2"));
_DataTable.Columns.Add(new DataColumn("Col3"));

for (int i = 0; i < 10; i++) {
    DataRow _DataRow = _DataTable.NewRow();
    _DataRow["Col1"] = (i + 1) + "Column 1";
    _DataRow["Col2"] = (i + 1) + "Column 2";
    _DataRow["Col3"] = (i + 1) + "Column 3";
    _DataTable.Rows.Add(_DataRow);
}

byte[] ByteArrayTest =  _DataTable.Serializer();
DataTable dt = ByteArrayTest.Deserializer<DataTable>();
Frank Myat Do.
quelle
6

Verwenden Encoding.UTF8.GetBytesist schneller als Verwenden MemoryStream. Hier verwende ich NewtonsoftJson , um das Eingabeobjekt in eine JSON-Zeichenfolge zu konvertieren und dann Bytes von der JSON-Zeichenfolge abzurufen .

byte[] SerializeObject(object value) =>Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value));

Benchmark für die Version von @Daniel DiPaolo mit dieser Version

Method                    |     Mean |     Error |    StdDev |   Median |  Gen 0 | Allocated |
--------------------------|----------|-----------|-----------|----------|--------|-----------| 
ObjectToByteArray         | 4.983 us | 0.1183 us | 0.2622 us | 4.887 us | 0.9460 |    3.9 KB |
ObjectToByteArrayWithJson | 1.548 us | 0.0309 us | 0.0690 us | 1.528 us | 0.3090 |   1.27 KB |
kiran
quelle
2

Kombinierte Lösungen in der Erweiterungsklasse:

public static class Extensions {

    public static byte[] ToByteArray(this object obj) {
        var size = Marshal.SizeOf(data);
        var bytes = new byte[size];
        var ptr = Marshal.AllocHGlobal(size);
        Marshal.StructureToPtr(data, ptr, false);
        Marshal.Copy(ptr, bytes, 0, size);
        Marshal.FreeHGlobal(ptr);
        return bytes;
   }

    public static string Serialize(this object obj) {
        return JsonConvert.SerializeObject(obj);
   }

}
Fehler 404
quelle
1

Sie könnten die Verwendung Einbau-Serialisierung - Tool im Rahmen und serialize zu einem Memorystream . Dies ist möglicherweise die einfachste Option, erzeugt jedoch möglicherweise ein größeres Byte [], als für Ihr Szenario unbedingt erforderlich ist.

In diesem Fall können Sie mithilfe der Reflexion die Felder und / oder Eigenschaften des zu serialisierenden Objekts durchlaufen und manuell in den MemoryStream schreiben. Bei Bedarf wird die Serialisierung rekursiv aufgerufen, um nicht triviale Typen zu serialisieren. Diese Methode ist komplexer und benötigt mehr Zeit für die Implementierung, ermöglicht Ihnen jedoch eine wesentlich bessere Kontrolle über den serialisierten Stream.


quelle
1

Wie wäre es mit so etwas Einfachem?

return ((object[])value).Cast<byte>().ToArray(); 
Peter Kozak
quelle
1

Ich würde lieber den Ausdruck "Serialisierung" als "Umwandlung in Bytes" verwenden. Das Serialisieren eines Objekts bedeutet, dass es in ein Byte-Array (oder XML oder etwas anderes) konvertiert wird, das auf der Remote-Box zum Rekonstruieren des Objekts verwendet werden kann. In .NET markiert das SerializableAttribut Typen, deren Objekte serialisiert werden können.

Matthias Meid
quelle
1

Alternative Möglichkeit, ein Objekt in ein Byte-Array zu konvertieren:

TypeConverter objConverter = TypeDescriptor.GetConverter(objMsg.GetType());
byte[] data = (byte[])objConverter.ConvertTo(objMsg, typeof(byte[]));
Khoa Nguyen
quelle
Versuchte dies, es schien nicht für mich unter .NET 4.6.1 und Windows 10 zu funktionieren.
Contango
0

Eine zusätzliche Implementierung, die Newtonsoft.Json- Binär-JSON verwendet und nicht erfordert, dass alles mit dem Attribut [Serializable] markiert wird. Der einzige Nachteil besteht darin, dass ein Objekt in eine anonyme Klasse eingeschlossen werden muss, sodass sich das mit der binären Serialisierung erhaltene Byte-Array von diesem unterscheiden kann.

public static byte[] ConvertToBytes(object obj)
{
    using (var ms = new MemoryStream())
    {
        using (var writer = new BsonWriter(ms))
        {
            var serializer = new JsonSerializer();
            serializer.Serialize(writer, new { Value = obj });
            return ms.ToArray();
        }
    }
}

Anonyme Klasse wird verwendet, da BSON mit einer Klasse oder einem Array beginnen sollte. Ich habe nicht versucht, Byte [] zurück zum Objekt zu deserialisieren und bin mir nicht sicher, ob es funktioniert, habe aber die Konvertierungsgeschwindigkeit in Byte [] getestet und es erfüllt meine Anforderungen vollständig.

prime_z
quelle
-2

Wie wäre es mit Serialisierung? Schauen Sie hier .

Itay Karo
quelle