Cara mengonversi objek menjadi array byte di C #


101

Saya memiliki koleksi objek yang perlu saya tulis ke file biner.

Saya membutuhkan byte dalam file agar kompak, jadi saya tidak bisa menggunakan BinaryFormatter. BinaryFormattermelempar semua jenis info untuk kebutuhan deserialization.

Jika saya mencoba

byte[] myBytes = (byte[]) myObject 

Saya mendapatkan pengecualian waktu proses.

Saya membutuhkan ini untuk menjadi cepat jadi saya lebih suka tidak menyalin array byte sekitar. Saya hanya ingin pemerannya byte[] myBytes = (byte[]) myObjectbekerja!

OK untuk memperjelas, saya tidak dapat memiliki metadata apa pun di file keluaran. Hanya byte objek. Paket objek-ke-objek. Berdasarkan jawaban yang diterima, sepertinya saya akan menulis Buffer.BlockCopykode tingkat rendah . Mungkin menggunakan kode yang tidak aman.

Jawaban:


176

Untuk mengonversi objek menjadi array 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();
    }
}

Anda hanya perlu menyalin fungsi ini ke kode Anda dan mengirimkannya objek yang perlu Anda ubah menjadi array byte. Jika Anda perlu mengubah array byte menjadi objek lagi, Anda dapat menggunakan fungsi di bawah ini:

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

Anda dapat menggunakan fungsi ini dengan kelas khusus. Anda hanya perlu menambahkan [Serializable]atribut di kelas Anda untuk mengaktifkan serialisasi


9
Saya mencoba ini dan menambahkan semua jenis metadata. OP mengatakan dia tidak menginginkan metadata.
pengguna316117

4
Belum lagi, semua orang tampaknya berasumsi bahwa apa yang Anda coba buat bersambung adalah sesuatu yang telah Anda tulis, atau sudah disiapkan sebelumnya untuk dijadikan serial.
Hexum064

3
Anda dapat meneruskan larik byte langsung ke pembuatnya MemoryStreamdi contoh kode kedua. Ini akan menghilangkan penggunaan Write(...)dan Seek(...).
Unknown6656

41

Jika Anda ingin data serial benar-benar ringkas, Anda dapat menulis metode serialisasi sendiri. Dengan begitu Anda akan memiliki biaya overhead minimum.

Contoh:

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

}

apa yang saya punya beberapa int untuk ditulis, dan beberapa string?
Smith

1
@Smith: Ya, Anda dapat melakukan itu, cukup tulis satu sama lain. Mereka BinaryWriterakan menuliskannya dalam format yang BinaryReaderdapat dibaca, selama Anda menulis dan membacanya dalam urutan yang sama.
Guffa

1
apa perbedaan antara BinaryWriter/Readerdan menggunakanBinaryFormatter
Smith

3
@Smith: Dengan menggunakan BinaryWriter/ReaderAnda melakukan serialisasi / deserialisasinya sendiri, dan Anda hanya dapat menulis / membaca data yang benar-benar diperlukan, sekompak mungkin. The BinaryFormatterpenggunaan refleksi untuk mengetahui data apa yang harus menulis / membaca, dan menggunakan format yang bekerja untuk semua kasus yang mungkin. Ini juga mencakup informasi meta tentang format di aliran, sehingga menambah lebih banyak overhead.
Guffa

1
@Smith: Anda dapat mentransmisikan enum ke int(atau jika Anda telah menentukan jenis lain sebagai penyimpanan untuk enum) dan menulisnya. Saat Anda membacanya, Anda dapat mentransmisikannya ke tipe enum.
Guffa

31

Nah cor dari myObjectke byte[]tidak akan pernah bekerja kecuali Anda punya konversi eksplisit atau jika myObject adalah sebuah byte[]. Anda memerlukan kerangka serialisasi dari beberapa jenis. Ada banyak hal di luar sana, termasuk Protocol Buffer yang dekat dan saya sayangi. Ini cukup "ramping dan berarti" dalam hal ruang dan waktu.

Anda akan menemukan bahwa hampir semua framework serialisasi memiliki batasan signifikan pada apa yang dapat Anda serialisasi, namun - Protocol Buffer lebih dari beberapa, karena bersifat lintas platform.

Jika Anda dapat memberikan lebih banyak persyaratan, kami dapat membantu Anda lebih lanjut - tetapi tidak akan pernah semudah mentransmisikan ...

EDIT: Hanya untuk menanggapi ini:

Saya membutuhkan file biner saya untuk memuat byte objek. Hanya byte, tidak ada metadata apa pun. Paket objek-ke-objek. Jadi saya akan menerapkan serialisasi kustom.

Harap diingat bahwa byte dalam objek Anda cukup sering menjadi referensi ... jadi Anda harus mencari tahu apa yang harus dilakukan dengannya.

Saya menduga Anda akan menemukan bahwa merancang dan menerapkan kerangka kerja serialisasi khusus Anda sendiri lebih sulit daripada yang Anda bayangkan.

Saya pribadi akan merekomendasikan bahwa jika Anda hanya perlu melakukan ini untuk beberapa jenis tertentu, Anda tidak perlu repot-repot mencoba membuat kerangka serialisasi umum. Cukup terapkan metode instance dan metode statis di semua jenis yang Anda butuhkan:

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

Satu hal yang perlu diingat: semuanya menjadi lebih rumit jika Anda melibatkan warisan. Tanpa pewarisan, jika Anda tahu jenis yang Anda gunakan untuk memulai, Anda tidak perlu menyertakan informasi jenis apa pun. Tentu saja, ada juga masalah pembuatan versi - apakah Anda perlu khawatir tentang kompatibilitas mundur dan maju dengan berbagai versi tipe Anda?


Apakah lebih tepat bagi saya untuk menyebut ini sebagai "protobuf-csharp-port" (Google-code), atau "dotnet-protobufs" (Git)?
Marc Gravell

1
Saya membutuhkan file biner saya untuk memuat byte objek. Hanya byte, tidak ada metadata apa pun. Paket objek-ke-objek. Jadi saya akan menerapkan serialisasi kustom.
chuckhlogan

6
Risiko dari nol metadata adalah Anda sangat tidak toleran terhadap versi, karena sangat sedikit cara yang memungkinkan fleksibilitas sebelum terlambat. Buffer protokol cukup padat data. Apakah Anda benar-benar membutuhkan putaran sekrup ekstra?
Marc Gravell

@Marc: Dan tentu saja untuk bilangan bulat, PB bisa menjadi lebih padat daripada byte mentah ...
Jon Skeet

16

Saya mengambil jawaban Crystalonics dan mengubahnya menjadi metode penyuluhan. Saya berharap orang lain akan menganggapnya berguna:

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
Yang ini sangat berguna dan mudah !! Terima kasih.
MrHIDEn

13

Anda benar-benar berbicara tentang serialisasi, yang dapat memiliki banyak bentuk. Karena Anda menginginkan yang kecil dan biner, penyangga protokol dapat menjadi pilihan yang layak - memberikan toleransi versi dan portabilitas juga. Tidak seperti BinaryFormatter, format kawat buffer protokol tidak menyertakan semua jenis metadata; hanya penanda yang sangat singkat untuk mengidentifikasi data.

Di NET. Ada beberapa implementasi; khususnya

Saya dengan rendah hati berpendapat bahwa protobuf-net (yang saya tulis) memungkinkan lebih banyak penggunaan .NET-idiomatik dengan kelas C # yang khas (buffer protokol "biasa" cenderung menuntut pembuatan kode); sebagai contoh:

[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
Bahkan "penanda singkat" masih merupakan metadata. Pemahaman saya tentang apa yang diinginkan OP hanyalah data dalam objek. Jadi, misalnya, jika objeknya adalah sebuah struct dengan 2 bilangan bulat 32-bit, maka dia akan mengharapkan hasilnya menjadi array byte 8 byte.
pengguna316117

@ user316117 yang sangat merepotkan untuk pembuatan versi. Setiap pendekatan memiliki kelebihan dan kekurangan.
Marc Gravell


Adakah cara untuk menghindari penggunaan atribut Proto *? Entitas yang ingin saya gunakan ada di perpustakaan pihak ketiga.
Alex 75

5

Ini berhasil untuk saya:

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

foo adalah Objek yang saya yakin 100% adalah array byte.


2

Lihatlah Serialization , teknik untuk "mengubah" seluruh objek menjadi aliran byte. Anda dapat mengirimkannya ke jaringan atau menuliskannya menjadi sebuah file dan kemudian mengembalikannya kembali ke suatu objek nanti.


Saya pikir chuckhlogan secara eksplisit menolak itu (Formatter == Serialization).
Henk Holterman

@ Henk - tergantung apa alasannya ; dia menyebutkan info tambahan, yang saya anggap metadata jenis dan info lapangan; Anda dapat menggunakan serialisasi tanpa overhead itu; tidak dengan BinaryFormatter.
Marc Gravell

2

Saya menemukan cara lain untuk mengonversi objek menjadi byte [], inilah solusi saya:

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

Salam


1

Untuk mengakses memori suatu objek secara langsung (untuk melakukan "core dump"), Anda harus membuka kode yang tidak aman.

Jika Anda menginginkan sesuatu yang lebih ringkas daripada yang diberikan BinaryWriter atau yang diberikan oleh raw memory dump, maka Anda perlu menulis beberapa kode serialisasi khusus yang mengekstrak informasi penting dari objek dan mengemasnya secara optimal.

edit PS Sangat mudah untuk menggabungkan pendekatan BinaryWriter ke dalam DeflateStream untuk memampatkan data, yang biasanya akan mengurangi setengah ukuran data.


1
Kode yang tidak aman tidak cukup. C # dan CLR tetap tidak mengizinkan Anda mengambil pointer mentah ke objek yang dikelola bahkan dalam kode yang tidak aman, atau meletakkan dua referensi objek dalam satu kesatuan.
Pavel Minaev

1

Saya percaya apa yang Anda coba lakukan tidak mungkin.

Sampah yang BinaryFormatterdibuat diperlukan untuk memulihkan objek dari file setelah program Anda dihentikan.
Bagaimanapun juga mungkin untuk mendapatkan data objek, Anda hanya perlu mengetahui ukuran pastinya (lebih sulit daripada kedengarannya):

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

ini dapat dipulihkan melalui:

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

Alasan mengapa metode di atas tidak berfungsi untuk serialisasi adalah karena empat byte pertama dalam data yang dikembalikan sesuai dengan a RuntimeTypeHandle. The RuntimeTypeHandlemenggambarkan tata letak / jenis objek tetapi nilai itu berubah setiap kali program ini adalah berlari.

EDIT: itu bodoh, jangan lakukan itu -> Jika Anda sudah mengetahui jenis objek yang akan dideserialisasi dengan pasti, Anda dapat mengganti byte tersebut BitConvertes.GetBytes((int)typeof(yourtype).TypeHandle.Value)pada saat deserialisasi.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.