2014-06-20 11 views
7

Ho un semplice oggetto che assomiglia a questo:fisso oggetto di array di byte

public class Foo 
{ 
    public UInt32 One { get; set; } 
    public UInt32 Two { get; set; } 
    public UInt32 Three { get; set; } 
    public UInt32 Four { get; set; } 
} 

ho provato questo codice che ho trovato da qualche parte in rete:

public byte[] ObjectToByteArray(Object obj) 
{ 
    MemoryStream fs = new MemoryStream(); 
    BinaryFormatter formatter = new BinaryFormatter(); 
    formatter.Serialize(fs, obj); 
    byte[] rval = fs.ToArray(); 
    fs.Close(); 
    return rval; 
} 

Ma in qualche modo l'array di byte di ritorno ha una dimensione di 248 byte.
Mi aspetterei che siano 4 byte x 4 campi = 16 byte.

DOMANDA:
Qual è il modo più pulito per convertire un oggetto fisso in un array di byte?
E in questo caso l'array risultante ha una dimensione di 16 byte?

risposta

6

BinaryFormatter fa risparmiare un sacco di informazioni sul tipo di essere in grado di deserializzare correttamente. Se si desidera che la serializzazione compatta o di comunicare tramite un protocollo rigoroso, si dovrà farlo esplicitamente in questo modo:

public byte[] ToByteArray() 
{ 
    List<byte> result = new List<byte>(); 

    result.AddRange(BitConverter.GetBytes(One)); 
    result.AddRange(BitConverter.GetBytes(Two)); 
    result.AddRange(BitConverter.GetBytes(Three)); 
    result.AddRange(BitConverter.GetBytes(Four)); 

    return result.ToArray(); 
} 

qui posso convertire ognuno dei vostri UInt32 in array di byte e riporlo in risultante array.

Modifica
scopre che c'è un altro modo utilizzando struct e Marshal Prima di effettuare struct e segnare con gli attributi del genere:

[StructLayout(LayoutKind.Sequential, Pack = 1)] 
struct MyStruct 
{ 
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)] 
    public string StringField; 

    public int IntField; 
} 

Qui LayoutKind.Sequential dice a CLR per mantenere i campi in memoria nello stesso ordine della dichiarazione. Senza le strutture Pack = 1 le strutture possono richiedere più memoria del necessario. Come struct con un campo short e uno byte richiede solo 3 byte, ma per impostazione predefinita la dimensione sarà molto probabilmente 4 (il processore ha istruzioni per manipolare un singolo byte, 2 byte e 4 byte, clr sacrifica un byte per ogni istanza della struttura per ridurre l'importo delle istruzioni del codice macchina della metà). Ora è possibile utilizzare per copiare Marshal byte:

public static byte[] GetBytes<T>(T str) 
{ 
    int size = Marshal.SizeOf(str); 
    var bytes = new byte[size]; 
    IntPtr ptr = Marshal.AllocHGlobal(size); 

    try 
    { 
     Marshal.StructureToPtr(str, ptr, true); 
     Marshal.Copy(ptr, bytes, 0, size); 
     return bytes; 
    } 
    finally 
    { 
     Marshal.FreeHGlobal(ptr); 
    } 
} 

Tutto funziona bene con i tipi semplici. Per tipi complessi come string devi utilizzare l'attributo MarshalAs e il suo utilizzo è un po 'più complicato (nell'esempio ho detto clr alla stringa di marshall come array di dimensione fissa di 50 byte).

2

Ricordare che quando si serializza un oggetto con BinaryFormatter, vengono incluse cose come le informazioni sul tipo. Ciò si traduce nel sovraccarico che stai vedendo.

Se si desidera serializzare un oggetto in (in questo caso) solo 16 byte, sarà necessario farlo manualmente con qualcosa come StreamWriter.

Se la dimensione non è un problema, un formato Binary in questo caso non è sbagliato e non richiede molto codice per farlo, ma non è il modo più efficiente in termini di memoria per farlo.

Edit: Ecco come si farebbe con un StreamWriter

System.IO.MemoryStream stream = new System.IO.MemoryStream(); 

StreamWriter writer = new StreamWriter(stream); 

writer.Write(myObject.One); // here's where we actually write the data to the stream 
writer.Write(myObject.Two); 
writer.Write(myObject.Three); 
writer.Write(myObject.Four);  

writer.Flush(); // make sure all the data in the stream writer ends up in the 
        // underlying stream 

byte[] result = stream.ToArray(); // here's your resulting byte array 

stream.Dispose(); // don't forget to dispose of the stream!   
+0

Deve essere 16 byte sì. Qualche suggerimento come dovrei usare lo StreamWriter ?? –

+0

Ho modificato la mia risposta. La matrice di byte risultante dovrebbe essere di 16 byte se lo si fa nel modo che suggerisco. Si noti che questo è solo codice di esempio. Non l'ho testato, né usa un'istruzione 'using' che dovresti usare per oggetti che implementano' IDisposable' come 'MemoryStream'. Potresti decidere di farne una funzione all'interno dell'oggetto Foo, quindi puoi chiamare foo.Serialize() per ottenere il tuo array di byte. –

2

Ecco un modo per farlo manualmente che garantirà i 16 byte.

using System; 
using System.IO; 
using System.Linq; 

public class Foo 
{ 
    public UInt32 One { get; set; } 
    public UInt32 Two { get; set; } 
    public UInt32 Three { get; set; } 
    public UInt32 Four { get; set; } 

    public Foo() {} 

    public Foo(byte[] array) 
    { 
     One = BitConverter.ToUInt32(array,0);  
     Two = BitConverter.ToUInt32(array,4); 
     Three = BitConverter.ToUInt32(array,8);  
     Four = BitConverter.ToUInt32(array,12);  
    } 
    public byte[] toByteArray() 
    { 
     byte[] retVal = new byte[16]; 
     byte[] b = BitConverter.GetBytes(One); 
     Array.Copy(b, 0, retVal, 0, 4); 
     b = BitConverter.GetBytes(Two); 
     Array.Copy(b, 0, retVal, 4, 4); 
     b = BitConverter.GetBytes(Three); 
     Array.Copy(b, 0, retVal, 8, 4); 
     b = BitConverter.GetBytes(Four); 
     Array.Copy(b, 0, retVal, 12, 4); 
     return retVal; 
    } 
} 
public class P{ 
    public static void Main(string[] args) { 
     Foo foo = new Foo(); 
     foo.One = 1; 
     foo.Two = 2; 
     foo.Three = 3; 
     foo.Four = 4; 

     byte[] arr = foo.toByteArray(); 
     Console.WriteLine(arr.Length); 


     Foo bar = new Foo(arr); 
     Console.WriteLine(string.Format("{0} {1} {2} {3}", bar.One, bar.Two, bar.Three, bar.Four)); 

    } 
} 

uscita:

16 
1 2 3 4 
2

Ecco un altro modo ... da che parte è la cosa migliore è una questione di opinione, probabilmente. Mi piace la risposta di Atomosk. Combina questa risposta con gli overload dell'operatore del cast e hai una soluzione piuttosto elegante.

class Foo 
{ 
    public UInt32 One { get; set; } 
    public UInt32 Two { get; set; } 
    public UInt32 Three { get; set; } 
    public UInt32 Four { get; set; } 

    static public implicit operator byte[](Foo f) 
    { 
     MemoryStream m = new MemoryStream(16); 
     BinaryWriter w = new BinaryWriter(m); 

     w.Write(f.One); 
     w.Write(f.Two); 
     w.Write(f.Three); 
     w.Write(f.Four); 
     w.Close(); 
     m.Close(); 

     return m.ToArray(); 
    } 

    static public explicit operator Foo(byte[] b) 
    { 
     Foo f = new Foo(); 
     MemoryStream m = new MemoryStream(b); 
     BinaryReader r = new BinaryReader(m); 

     f.One = r.ReadUInt32(); 
     f.Two = r.ReadUInt32(); 
     f.Three = r.ReadUInt32(); 
     f.Four = r.ReadUInt32(); 

     r.Close(); 
     m.Close(); 

     return f; 
    } 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     Foo f = new Foo() { One = 1, Two = 2, Three = 3, Four = 4 }; 
     byte[] b = (byte[])f; 
     Console.WriteLine(b.Length); 

     f = (Foo)b; 
     Console.WriteLine("{0} {1} {2} {3}", f.One, f.Two, f.Three, f.Four); 

     Console.ReadKey(true); 
    } 
}