Konversikan objek apa pun ke byte []

138

Saya sedang menulis koneksi TCP prototipe dan saya mengalami beberapa kesulitan menyeragamkan data yang akan dikirim.

Saat ini, saya tidak mengirim apa pun kecuali string, tetapi di masa depan kami ingin dapat mengirim objek apa pun.

Kode ini cukup sederhana saat ini, karena saya pikir semuanya dapat dilemparkan ke dalam array 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[]'.
  ...
}

Ini tentu saja cukup mudah diselesaikan dengan

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

Masalahnya adalah, jika saya melakukannya dengan cara itu, saya perlu memeriksa SETIAP jenis objek yang tidak dapat dilemparkan ke byte [] saat runtime.

Karena saya tidak tahu setiap objek yang tidak dapat dilemparkan ke byte [] pada saat runtime, ini sebenarnya bukan pilihan.

Bagaimana cara mengubah objek apa pun menjadi array byte di C # .NET 4.0?

Steve H.
sumber
2
Ini tidak mungkin dilakukan dengan cara yang berarti secara umum (pertimbangkan, misalnya, instance dari FileStream, atau objek apa pun yang merangkum pegangan seperti itu).
jason
2
Apakah Anda ingin semua klien menjalankan .NET? Jika jawabannya tidak, Anda harus mempertimbangkan beberapa bentuk serialisasi lainnya (XML, JSON, atau sejenisnya)
R. Martinho Fernandes

Jawaban:

195

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

Perhatikan bahwa objdan semua properti / bidang dalam obj(dan seterusnya untuk semua properti / bidangnya) semua harus ditandai dengan Serializableatribut agar berhasil diserialisasi dengan ini.

Daniel DiPaolo
sumber
13
Berhati-hatilah dengan apa yang Anda lakukan dengan objek "apa pun" di sisi lain, karena mungkin tidak lagi masuk akal (misalnya, jika objek itu adalah pegangan untuk file, atau yang serupa)
Rowland Shaw
1
Ya, peringatan normal berlaku, tetapi itu bukan ide yang buruk untuk mengingatkan orang-orang tentang mereka.
Daniel DiPaolo
24
Mungkin ide yang baik untuk membungkus penggunaan MemoryStream dalam sebuah usingblok, karena ia akan dengan bersemangat melepaskan buffer internal yang digunakan.
R. Martinho Fernandes
1
Apakah metode ini .NET terikat? Bisakah saya membuat serial C struct dengan StructLayoutAtrribute dan mengirim melalui socket ke kode C dan berharap bahwa kode C memahami struct? Saya rasa tidak?
joe
103

checkout artikel ini: http://www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

Gunakan kode di bawah ini

// 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
sumber
10
Seperti disebutkan dalam komentar untuk jawaban ini , MemorySteamharus dibungkus dalam satu usingblok.
rookie1024
adakah yang harus saya hormati dalam adition? Saya menerapkannya seperti itu dan Memformat Objek yang berisi 3 anggota publik int32 menghasilkan panjang ByteArray 244 Bytes. Apakah saya tidak mengetahui sesuatu tentang sintaksis C # atau ada sesuatu yang mungkin saya lewatkan gunakan?
dhein
Maaf, saya tidak bisa mendapatkan masalah Anda. Bisakah Anda memposting kode?
kombsh
@ kombsh Saya coba dalam bentuk singkat: [Serializable] class GameConfiguration {public map_options_t enumMapIndex; iPlayerAmount Int32 publik; private Int32 iGameID; } byte [] baPacket; GameConfiguration objGameConfClient = GameConfiguration baru (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); Sekarang baPacket berisi sekitar 244 Bytes konten. Saya hanya berharap 12.
dhein
1
@ kombsh Anda dapat secara eksplisit membuang objek sekali pakai dalam contoh Anda.
Rudolf Dvoracek
30

Seperti orang lain katakan sebelumnya, Anda bisa menggunakan serialisasi biner, tetapi mungkin menghasilkan byte tambahan atau deserialized menjadi objek dengan data yang tidak persis sama. Menggunakan refleksi di sisi lain cukup rumit dan sangat lambat. Ada solusi lain yang secara ketat dapat mengkonversi objek Anda menjadi byte dan sebaliknya - 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);

Dan untuk mengonversi byte ke objek:

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

Terlihat lebih lambat dan sebagian tidak aman untuk menggunakan pendekatan ini untuk objek dan struct kecil dibandingkan dengan bidang serialisasi Anda sendiri berdasarkan bidang (karena penyalinan ganda dari / ke memori yang tidak dikelola), tetapi cara termudah untuk secara ketat mengkonversi objek ke byte [] tanpa menerapkan serialisasi dan tanpa atribut [Serializable].

Aberro
sumber
1
Mengapa menurut Anda StructureToPtr+ Copylambat? Bagaimana bisa lebih lambat dari serialisasi? Apakah ada solusi yang lebih cepat?
Anton Samsonov
Jika Anda menggunakannya untuk struct kecil yang terdiri dari beberapa jenis sederhana, ya (yang merupakan kasus yang cukup umum), itu lambat karena marshalling dan menyalin quad (dari objek ke heap, dari heap ke byte, dari byte ke heap, dari heap ke objek). Bisa jadi lebih cepat ketika IntPtr digunakan alih-alih byte, tetapi tidak dalam kasus ini. Dan lebih cepat untuk jenis seperti itu untuk menulis serializer sendiri yang hanya menempatkan nilai ke dalam array byte. Saya tidak mengatakan itu lebih lambat daripada serialisasi built-in atau bahwa itu "sangat sangat lambat".
Aberro
1
Saya suka metode ini karena memetakan byte-by-byte. Ini adalah metode yang sangat baik untuk bertukar memori dengan pemetaan C ++. +1 untuk Anda.
Hao Nguyen
2
Catatan untuk pengguna potensial, walaupun sangat cerdas, jawaban ini tidak berfungsi pada susunan susunan, objek yang tidak dapat disusun sebagai struktur atau objek yang tidak dikelola yang memiliki induk (salah) ComVisible dalam hierarki mereka.
TernaryTopiary
1
Untuk mensterilkan bagaimana Anda mendapatkan "ukuran"? divar bytes = new byte[size];
Ricardo
13

Apa yang Anda cari adalah serialisasi. Ada beberapa bentuk serialisasi yang tersedia untuk platform .Net

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

Anda dapat menggunakannya seperti kode di bawah ini.

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 Thu
sumber
6

Menggunakan Encoding.UTF8.GetByteslebih cepat daripada menggunakan MemoryStream. Di sini, saya menggunakan NewtonsoftJson untuk mengkonversi objek input ke string JSON dan kemudian mendapatkan byte dari string JSON.

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

Benchmark untuk versi @Daniel DiPaolo dengan versi ini

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
sumber
2

Solusi Gabungan di kelas Ekstensi:

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

}
Kesalahan 404
sumber
1

Anda bisa menggunakan alat serialisasi built-in dalam framework dan membuat serial ke MemoryStream . Ini mungkin opsi yang paling mudah, tetapi mungkin menghasilkan byte yang lebih besar [] daripada yang diperlukan untuk skenario Anda.

Jika itu masalahnya, Anda bisa menggunakan refleksi untuk beralih pada bidang dan / atau properti dalam objek yang akan diserialkan dan secara manual menuliskannya ke MemoryStream, memanggil serialisasi secara rekursif jika diperlukan untuk membuat serial jenis non-sepele. Metode ini lebih kompleks dan akan membutuhkan lebih banyak waktu untuk diterapkan, tetapi memungkinkan Anda lebih banyak mengontrol aliran serial.


sumber
1

Bagaimana dengan hal sederhana seperti ini?

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

Saya lebih suka menggunakan ekspresi "serialisasi" daripada "casting ke byte". Membuat serial objek berarti mengubahnya menjadi array byte (atau XML, atau yang lain) yang dapat digunakan pada kotak jauh untuk membangun kembali objek. Di .NET, Serializableatribut menandai tipe objek yang dapat diserialisasi.

Matthias Meid
sumber
1

Cara alternatif untuk mengubah objek ke byte array:

TypeConverter objConverter = TypeDescriptor.GetConverter(objMsg.GetType());
byte[] data = (byte[])objConverter.ConvertTo(objMsg, typeof(byte[]));
Khoa Nguyen
sumber
Mencoba ini, sepertinya tidak berhasil untuk saya di .NET 4.6.1 dan Windows 10.
Contango
0

Satu implementasi tambahan, yang menggunakan JSON biner Newtonsoft.Json dan tidak perlu menandai semuanya dengan atribut [Serializable]. Hanya satu kelemahan adalah bahwa suatu objek harus dibungkus dalam kelas anonim, sehingga array byte yang diperoleh dengan serialisasi biner dapat berbeda dari yang satu ini.

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

Kelas anonim digunakan karena BSON harus dimulai dengan kelas atau array. Saya belum mencoba deserialize byte [] kembali ke objek dan tidak yakin apakah itu berfungsi, tetapi telah menguji kecepatan konversi ke byte [] dan itu benar-benar memuaskan kebutuhan saya.

prime_z
sumber
-2

Bagaimana dengan serialisasi? lihat di sini .

Itay Karo
sumber