Come convertire un oggetto in una matrice di byte in C #


100

Ho una raccolta di oggetti che devo scrivere su un file binario.

Ho bisogno che i byte nel file siano compatti, quindi non posso usare BinaryFormatter. BinaryFormatterinserisce tutti i tipi di informazioni per le esigenze di deserializzazione.

Se provo

byte[] myBytes = (byte[]) myObject 

Ottengo un'eccezione di runtime.

Ho bisogno che sia veloce, quindi preferisco non copiare matrici di byte in giro. Vorrei solo che il cast byte[] myBytes = (byte[]) myObjectlavorasse!

OK tanto per essere chiari, non posso avere alcun metadati nel file di output. Solo i byte dell'oggetto. Da oggetto a oggetto. Sulla base delle risposte ricevute, sembra che scriverò Buffer.BlockCopycodice di basso livello . Forse usando un codice non sicuro.

Risposte:


174

Per convertire un oggetto in una matrice di byte:

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

Devi solo copiare questa funzione nel tuo codice e inviargli l'oggetto che devi convertire in un array di byte. Se è necessario convertire nuovamente l'array di byte in un oggetto, è possibile utilizzare la funzione seguente:

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

È possibile utilizzare queste funzioni con classi personalizzate. Devi solo aggiungere l' [Serializable]attributo nella tua classe per abilitare la serializzazione


9
Ho provato questo e ha aggiunto tutti i tipi di metadati. L'OP ha detto di non volere i metadati.
user316117

4
Per non parlare del fatto che tutti sembrano presumere che ciò che stai cercando di serializzare sia qualcosa che hai scritto o che è già stato preimpostato per essere serializzato.
Hexum064

3
È possibile passare la matrice di byte direttamente al costituente di MemoryStreamnel secondo esempio di codice. Ciò eliminerebbe l'uso di Write(...)e Seek(...).
sconosciuto 6656

41

Se vuoi che i dati serializzati siano veramente compatti, puoi scrivere tu stesso metodi di serializzazione. In questo modo avrai un minimo di overhead.

Esempio:

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

}

cos'è che ho diversi int da scrivere e diverse stringhe?
Smith

1
@ Smith: Sì, puoi farlo, basta scriverli uno dopo l'altro. Le BinaryWriterscriverà in un formato che BinaryReaderpossa leggere, purché tu le scriva e le legga nello stesso ordine.
Guffa

1
qual è la differenza tra BinaryWriter/Readere l'utilizzo di unBinaryFormatter
Smith

3
@ Smith: Usando BinaryWriter/Readerfai tu stesso la serializzazione / deserializzazione e puoi scrivere / leggere solo i dati che sono assolutamente necessari, il più compatti possibile. L' BinaryFormatteruso di riflessione per scoprire cosa i dati di lettura / scrittura, e utilizza un formato che funziona per tutti i casi possibili. Include anche le meta informazioni sul formato nello stream, in modo da aggiungere ancora più overhead.
Guffa

1
@ Smith: è possibile eseguire il cast dell'enumerazione int(o se è stato specificato un altro tipo come archivio per l'enumerazione) e scriverlo. Quando lo leggi, puoi trasmetterlo al tipo enum.
Guffa

31

Bene, un cast da myObjecta byte[]non funzionerà mai a meno che tu non abbia una conversione esplicita o se myObject è un file byte[]. Hai bisogno di un framework di serializzazione di qualche tipo. Ce ne sono molti là fuori, compresi i buffer di protocollo che mi sono vicini e cari. È piuttosto "snello e meschino" sia in termini di spazio che di tempo.

Scoprirai che quasi tutti i framework di serializzazione hanno restrizioni significative su ciò che puoi serializzare, tuttavia, i buffer di protocollo più di alcuni, a causa del fatto di essere multipiattaforma.

Se puoi fornire più requisiti, possiamo aiutarti di più, ma non sarà mai semplice come lanciare ...

EDIT: solo per rispondere a questo:

Ho bisogno che il mio file binario contenga i byte dell'oggetto. Solo i byte, nessun metadata di sorta. Da oggetto a oggetto. Quindi implementerò la serializzazione personalizzata.

Tieni presente che i byte nei tuoi oggetti sono spesso riferimenti ... quindi dovrai capire cosa farne.

Sospetto che scoprirai che progettare e implementare il tuo framework di serializzazione personalizzato è più difficile di quanto immagini.

Personalmente consiglierei che se hai bisogno di farlo solo per alcuni tipi specifici, non ti preoccupare di provare a trovare un framework di serializzazione generale. Basta implementare un metodo di istanza e un metodo statico in tutti i tipi di cui hai bisogno:

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

Una cosa da tenere a mente: tutto diventa più complicato se hai in gioco l'eredità. Senza ereditarietà, se sai con che tipo stai iniziando, non è necessario includere alcuna informazione sul tipo. Naturalmente, c'è anche la questione del controllo delle versioni: devi preoccuparti della compatibilità con versioni precedenti e successive dei tuoi tipi?


È più corretto per me riferirmi a questo come "protobuf-csharp-port" (codice Google) o "dotnet-protobufs" (Git)?
Marc Gravell

1
Ho bisogno che il mio file binario contenga i byte dell'oggetto. Solo i byte, nessun metadata di sorta. Da oggetto a oggetto. Quindi implementerò la serializzazione personalizzata.
chuckhlogan

6
Il rischio di zero metadati è che allora sei molto intollerante alla versione, poiché ha pochissimi modi per consentire la flessibilità prima che sia troppo tardi. I buffer di protocollo sono piuttosto densi di dati. Hai davvero bisogno di quel giro di vite in più?
Marc Gravell

@ Marc: E ovviamente per i numeri interi, PB può finire per essere più denso dei byte grezzi ...
Jon Skeet

16

Ho preso la risposta di Crystalonics e li ho trasformati in metodi di estensione. Spero che qualcun altro li trovi utili:

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
Questo è davvero utile e facile !! Grazie.
MrHIDE il

13

Stai davvero parlando di serializzazione, che può assumere molte forme. Dal momento che vuoi piccoli e binari, i buffer di protocollo possono essere un'opzione praticabile, dando anche tolleranza e portabilità alla versione. Diversamente BinaryFormatter, il formato wire dei buffer di protocollo non include tutti i metadati del tipo; solo marcatori molto concisi per identificare i dati.

In .NET ci sono alcune implementazioni; in particolare

Direi umilmente che protobuf-net (che ho scritto) consente un utilizzo più idiomatico di .NET con le classi C # tipiche (i buffer di protocollo "normali" tendono a richiedere la generazione di codice); per esempio:

[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
Anche i "marcatori concisi" sono ancora metadati. La mia comprensione di ciò che voleva l'OP non era altro che i dati nell'oggetto. Quindi, ad esempio, se l'oggetto fosse una struttura con 2 interi a 32 bit, allora si aspetterebbe che il risultato sia un array di byte di 8 byte.
user316117

@ user316117 che è quindi un vero problema per il controllo delle versioni. Ogni approccio presenta vantaggi e svantaggi.
Marc Gravell


C'è un modo per evitare di utilizzare gli attributi Proto *? Le entità che desidero utilizzare si trovano in una libreria di terze parti.
Alex 75

5

Questo ha funzionato per me:

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

foo è un oggetto di cui sono sicuro al 100% che sia un array di byte.


2

Dai un'occhiata alla serializzazione , una tecnica per "convertire" un intero oggetto in un flusso di byte. Puoi inviarlo alla rete o scriverlo in un file e poi ripristinarlo in un oggetto in un secondo momento.


Penso che chuckhlogan l'abbia esplicitamente rifiutato (Formatter == Serialization).
Henk Holterman

@ Henk - dipende da quali sono le ragioni ; ha menzionato le informazioni extra, che presumo siano metadati di tipo e informazioni sul campo; puoi usare la serializzazione senza questo sovraccarico; solo non con BinaryFormatter.
Marc Gravell

2

Ho trovato un altro modo per convertire un oggetto in un byte [], ecco la mia soluzione:

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

Saluti


1

Per accedere direttamente alla memoria di un oggetto (per fare un "core dump") dovrai entrare in un codice non sicuro.

Se vuoi qualcosa di più compatto di BinaryWriter o di un dump della memoria non elaborato, devi scrivere un codice di serializzazione personalizzato che estrae le informazioni critiche dall'oggetto e le impacchetta in modo ottimale.

modifica PS È molto facile avvolgere l'approccio BinaryWriter in un DeflateStream per comprimere i dati, il che di solito dimezza all'incirca la dimensione dei dati.


1
Il codice non sicuro non è sufficiente. C # e CLR non ti consentono ancora di prendere un puntatore non elaborato a un oggetto gestito anche in codice non sicuro o di inserire due riferimenti a oggetti in un'unione.
Pavel Minaev

0

Credo che quello che stai cercando di fare sia impossibile.

La spazzatura che BinaryFormattercrea è necessaria per recuperare l'oggetto dal file dopo che il programma si è arrestato.
Tuttavia è possibile ottenere i dati dell'oggetto, è sufficiente conoscerne la dimensione esatta (più difficile di quanto sembri):

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

questo può essere recuperato tramite:

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

Il motivo per cui i metodi precedenti non funzionano per la serializzazione è che i primi quattro byte nei dati restituiti corrispondono a un file RuntimeTypeHandle. La RuntimeTypeHandledescrive il layout / tipo di oggetto, ma il valore di esso cambia ogni volta che il programma è Ran.

EDIT: è stupido non farlo -> Se conosci già il tipo di oggetto da deserializzare per certo, puoi cambiare quei byte per BitConvertes.GetBytes((int)typeof(yourtype).TypeHandle.Value)al momento della deserializzazione.

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.