Converti qualsiasi oggetto in un byte []


138

Sto scrivendo un prototipo di connessione TCP e non riesco a omogeneizzare i dati da inviare.

Al momento non sto inviando altro che stringhe, ma in futuro vogliamo poter inviare qualsiasi oggetto.

Il codice è abbastanza semplice al momento, perché pensavo che tutto potesse essere lanciato in un array di byte:

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[]'.
  ...
}

Questo ovviamente è abbastanza facilmente risolto con a

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

Il problema è che, se lo faccio in questo modo, devo controllare OGNI tipo di oggetto che non può essere lanciato su un byte [] in fase di esecuzione.

Dato che non conosco tutti gli oggetti che non possono essere lanciati in un byte [] in fase di esecuzione, questa non è un'opzione.

Come si converte qualsiasi oggetto in un array di byte in C # .NET 4.0?


2
Ciò non è possibile in alcun modo significativo in generale (si consideri, ad esempio, un'istanza FileStreamo qualsiasi oggetto che incapsuli un handle del genere).
Jason,

2
Vuoi avere tutti i client che eseguono .NET? Se la risposta è no, dovresti prendere in considerazione un'altra forma di serializzazione (XML, JSON o simili)
R. Martinho Fernandes,

Risposte:


195

Usa il 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();
    }
}

Si noti che obje tutte le proprietà / campi all'interno obj(e così via per tutte le loro proprietà / campi) dovranno tutti essere taggati con l' Serializableattributo per essere serializzati con successo con questo.


13
Fai attenzione a ciò che fai con "qualsiasi" oggetto dall'altra parte, in quanto potrebbe non avere più senso (ad esempio, se quell'oggetto fosse un handle per un file o simile)
Rowland Shaw

1
Sì, si applicano normali avvertimenti, ma non è una cattiva idea ricordare loro.
Daniel DiPaolo,

24
Potrebbe essere una buona idea racchiudere l'utilizzo di MemoryStream in un usingblocco, poiché rilascerà con entusiasmo il buffer interno utilizzato.
R. Martinho Fernandes,

1
Questo metodo è associato a .NET? Posso serializzare una C struct con StructLayoutAtrribute e inviarlo tramite socket a un codice C e aspettarmi che il codice C comprenda la struttura? Non credo?
joe

103

leggi questo articolo: http://www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

Usa il codice qui sotto

// 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;
}

10
Come menzionato in un commento a questa risposta , MemorySteamdovrebbe essere racchiuso in un usingblocco.
rookie1024,

c'è qualcosa che devo rispettare nell'adorazione? L'ho implementato in questo modo e la formattazione di un oggetto contenente 3 membri pubblici int32 risulta in un ByteArray lungo 244 byte. Non sto conoscendo qualcosa sulla sintassi di C # o c'è qualcosa che probabilmente mi mancherebbe usare?
Dhein,

Scusa, non riesco a capire il tuo problema. Puoi pubblicare il codice?
kombsh,

@kombsh Provo in forma abbreviata: [Serializable] class GameConfiguration {public map_options_t enumMapIndex; public Int32 iPlayerAmount; iGameID privato Int32; } byte [] baPacket; GameConfiguration objGameConfClient = new GameConfiguration (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); Ora baPacket contiene circa 244 byte di contenuto f. Mi aspettavo 12.
Dhein

1
@kombsh è possibile disporre esplicitamente oggetti usa e getta nel proprio esempio.
Rudolf Dvoracek,

30

Come altri hanno già detto in precedenza, è possibile utilizzare la serializzazione binaria, ma può produrre byte aggiuntivi o essere deserializzato in oggetti con dati non esattamente identici. L'uso della riflessione invece è piuttosto complicato e molto lento. Esiste un'altra soluzione che può convertire rigorosamente i tuoi oggetti in byte e viceversa: il 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);

E per convertire i byte in oggetto:

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);

È notevolmente più lento e parzialmente non sicuro utilizzare questo approccio per piccoli oggetti e strutture rispetto al proprio campo di serializzazione campo per campo (a causa della doppia copia da / in memoria non gestita), ma è il modo più semplice per convertire rigorosamente l'oggetto in byte [] senza implementare la serializzazione e senza l'attributo [Serializable].


1
Perché pensi che StructureToPtr+ Copysia lento? Come può essere più lento della serializzazione? C'è qualche soluzione più veloce?
Anton Samsonov,

Se lo usi per piccole strutture costituite da pochi tipi semplici, sì (che è un caso abbastanza comune), è lento a causa del marshalling e della copia quad (dall'oggetto all'heap, dall'heap ai byte, dai byte all'heap, dall'heap obiettare). Potrebbe essere più veloce quando si utilizza IntPtr anziché byte, ma non in questo caso. Ed è più veloce per questi tipi scrivere il proprio serializzatore che inserisce semplicemente valori nell'array di byte. Non sto dicendo che sia più lento della serializzazione integrata né che sia "così dannatamente lento".
Aberro,

1
Mi piace questo metodo poiché mappa byte per byte. Questo è un ottimo metodo per scambiare memoria con la mappatura C ++. +1 per te.
Hao Nguyen,

2
Nota per i potenziali utenti, sebbene molto intelligente, questa risposta non funziona su array di strutture, oggetti che non possono essere sottoposti a marshalling come strutture non gestite o oggetti che hanno un genitore ComVisible (false) nella loro gerarchia.
TernaryTopiary

1
Per deserilizzare come hai ottenuto la "dimensione"? invar bytes = new byte[size];
Ricardo

13

Quello che stai cercando è la serializzazione. Esistono diverse forme di serializzazione disponibili per la piattaforma .Net


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;
    }
}

Puoi usarlo come sotto il codice.

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>();

6

L'uso Encoding.UTF8.GetBytesè più veloce dell'uso MemoryStream. Qui, sto usando NewtonsoftJson per convertire l'oggetto di input in una stringa JSON e quindi ottenere byte dalla stringa JSON.

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

Punto di riferimento per la versione di @Daniel DiPaolo con questa versione

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 |

2

Soluzioni combinate in classe di estensioni:

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);
   }

}

1

È possibile utilizzare gli strumenti di serializzazione integrati nel framework e serializzare su MemoryStream . Questa potrebbe essere l'opzione più semplice, ma potrebbe produrre un byte più grande [] di quanto potrebbe essere strettamente necessario per il tuo scenario.

In tal caso, è possibile utilizzare la riflessione per scorrere i campi e / o le proprietà dell'oggetto da serializzare e scriverli manualmente su MemoryStream, chiamando la serializzazione in modo ricorsivo se necessario per serializzare tipi non banali. Questo metodo è più complesso e richiederà più tempo per l'implementazione, ma consente un controllo molto maggiore sul flusso serializzato.


1

Che ne dici di qualcosa di semplice come questo?

return ((object[])value).Cast<byte>().ToArray(); 

1

Preferirei usare l'espressione "serializzazione" piuttosto che "casting in byte". Serializzare un oggetto significa convertirlo in un array di byte (o XML, o qualcos'altro) che può essere utilizzato sulla scatola remota per ricostruire l'oggetto. In .NET, l' Serializableattributo contrassegna i tipi i cui oggetti possono essere serializzati.


1

Modo alternativo per convertire l'oggetto in array di byte:

TypeConverter objConverter = TypeDescriptor.GetConverter(objMsg.GetType());
byte[] data = (byte[])objConverter.ConvertTo(objMsg, typeof(byte[]));

Ho provato questo, non sembrava funzionare su .NET 4.6.1 e Windows 10.
Contango,

0

Un'implementazione aggiuntiva, che utilizza JSON binario Newtonsoft.Json e non richiede di contrassegnare tutto con l'attributo [Serializable]. L'unico inconveniente è che un oggetto deve essere racchiuso in una classe anonima, quindi l'array di byte ottenuto con la serializzazione binaria può essere diverso da questo.

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();
        }
    }
}

La classe anonima viene utilizzata perché BSON dovrebbe iniziare con una classe o un array. Non ho provato a deserializzare il byte [] di nuovo sull'oggetto e non sono sicuro che funzioni, ma ho testato la velocità di conversione in byte [] e soddisfa completamente le mie esigenze.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.