Перетворити будь-який об’єкт у байт []


138

Я пишу прототип TCP-з'єднання, і у мене виникають проблеми з гомогенізацією даних, що надсилаються.

Наразі я не надсилаю нічого, крім рядків, але в майбутньому ми хочемо мати можливість надіслати будь-який об’єкт.

На даний момент код досить простий, тому що я думав, що все може бути передано в байтовий масив:

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

Звичайно, це досить легко вирішити за допомогою a

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

Проблема полягає в тому, що якщо я це роблю таким чином, мені потрібно перевірити ВСЕ тип об'єкта, який не може бути переданий до байту [] під час виконання.

Оскільки я не знаю кожного об'єкта, який не можна передавати в байт [] під час виконання, це насправді не є варіантом.

Як можна взагалі перетворити будь-який об’єкт у масив байтів у C # .NET 4.0?


2
Це взагалі неможливо в будь-який змістовний спосіб (розглянемо, наприклад, екземпляр FileStreamабо будь-який об'єкт, який інкапсулює ручку таким чином).
ясон

2
Ви маєте намір мати всіх клієнтів .NET? Якщо відповідь «ні», слід розглянути ще одну форму серіалізації (XML, JSON або подібні)
R. Martinho Fernandes

Відповіді:


195

Використовуйте 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();
    }
}

Зауважте, що для objбудь-яких властивостей / полів у межах obj(і так далі для всіх їх властивостей / полів) потрібно буде позначати Serializableатрибут, щоб успішно їх серіалізувати.


13
Будьте уважні з тим, що ви робите з "будь-яким" об'єктом з іншого боку, оскільки це може бути вже не має сенсу (наприклад, якщо цей об'єкт був ручкою до файлу чи подібним)
Rowland Shaw

1
Так, звичайні застереження застосовуються, але це не погана ідея нагадувати людям про них.
Даніель Діпаоло

24
Можливо, буде гарною ідеєю обернути використання MemoryStream в usingблок, оскільки це нетерпляче звільнить використаний внутрішній буфер.
Р. Мартіньо Фернандес

1
Чи пов'язаний цей метод .NET? Чи можу я серіалізувати структуру С за допомогою StructLayoutAtrribute та надіслати через сокет до коду С і очікувати, що код C розуміє структуру? Я думаю, що не?
Джо

103

перевірити цю статтю: http://www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

Скористайтеся наведеним нижче кодом

// 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
Як згадується в коментарі до цієї відповіді , MemorySteamслід загорнути в usingблок.
rookie1024

чи є щось, що я маю поважати при нагоді? Я реалізував це таким чином і Форматування об'єкта, що містить 3 публічних члена int32, призводить до отримання ByteArray довжиною 244 байти. Я щось не знаю про синтаксис C # чи є щось, що я, ймовірно, пропустив би у використанні?
dhein

Вибачте, я не можу зрозуміти вашу проблему. Чи можете ви опублікувати код?
комбш

@kombsh Я пробую в короткій формі: [Serializable] клас GameConfiguration {public map_options_t enumMapIndex; загальнодоступний Int32 iPlayerAmount; приватний Int32 iGameID; } байт [] baPacket; GameConfiguration objGameConfClient = нова GameConfiguration (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); Зараз baPacket містить близько 244 байт f вмісту. Я очікував 12.
дев.

1
@kombsh ви можете явно розміщувати одноразові об’єкти у своєму прикладі.
Рудольф Дворачек

30

Як і раніше говорили інші, ви можете використовувати двійкову серіалізацію, але вона може створювати зайві байти або дезаріалізуватись в об'єкти з не зовсім однаковими даними. Використання рефлексії з іншого боку досить складне і дуже повільне. Є ще одне рішення, яке може строго перетворити ваші об'єкти в байти і навпаки - маршал:

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

І перетворити байти в об’єкт:

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

Помітно повільніше і частково небезпечно використовувати цей підхід для дрібних об'єктів і структур порівняно з вашим власним полем серіалізації за польовими (через подвійне копіювання з / в некеровану пам'ять), але це найпростіший спосіб суворо перетворити об'єкт у байт [], не застосовуючи серіалізацію і без атрибута [Serializable].


1
Чому ти вважаєш StructureToPtr+ Copyповільним? Як це може бути повільніше, ніж серіалізація? Чи є швидше рішення?
Антон Самсонов

Якщо ви використовуєте його для невеликих конструкцій, що складаються з декількох простих типів, так (це досить поширений випадок), це повільно через маршалінг та квадрокопіювання (від об'єкта до купи, від купи до байтів, від байтів до купи, від купи заперечити). Це може бути швидше, коли замість байтів використовується IntPtr, але не в цьому випадку. І швидше для таких типів писати власний серіалізатор, який просто ставить значення в масив байтів. Я не кажу, що це повільніше, ніж вбудована серіалізація, ні що це "так проклято повільно".
Аберро

1
Мені подобається цей метод, оскільки він відображає по байтах. Це дійсно хороший метод обміну пам’яттю за допомогою зіставлення C ++. +1 для вас.
Hao Nguyen

2
Зверніть увагу на потенційних користувачів, хоча ця відповідь не дуже розумна для масивів структури, об'єктів, які не можуть бути розподілені як некерована структура, або об'єктів, у яких в їх ієрархії є ComVisible (помилковий) батьків.
TernaryTopiary

1
Десерилізувати, як ви отримали "розмір"? вvar bytes = new byte[size];
Рікардо


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

Ви можете використовувати його як наведений нижче код.

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

Використовувати Encoding.UTF8.GetBytesшвидше, ніж використовувати MemoryStream. Тут я використовую NewtonsoftJson для перетворення вхідного об'єкта в рядок JSON, а потім отримання байтів з рядка JSON.

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

Орієнтир для версії @Daniel DiPaolo з цією версією

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

Комбіновані рішення в класі розширень:

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

Ви можете використовувати вбудовані інструменти серіалізації в рамках і серіалізувати в MemoryStream . Це може бути найбільш простим варіантом, але може створити байт більшого розміру [], ніж може бути строго необхідним для вашого сценарію.

Якщо це так, ви можете використовувати відображення для ітерації над полями та / або властивостями об'єкта, що підлягає серіалізації, і вручну записати їх у MemoryStream, викликаючи серіалізацію рекурсивно, якщо потрібно для серіалізації нетривіальних типів. Цей метод є більш складним і потребуватиме більше часу для його впровадження, але дозволяє значно більше контролювати серіалізований потік.



1

Я вважаю за краще використовувати вираз "серіалізація", ніж "введення в байти". Серіалізація об'єкта означає перетворення його в байтовий масив (або XML або щось інше), який може бути використаний у віддаленому вікні для відновлення об'єкта. У .NET Serializableатрибут позначає типи, об’єкти яких можна серіалізувати.


1

Альтернативний спосіб перетворення об'єкта в байтовий масив:

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

Спробував це, мені здалося, що це не працює для .NET 4.6.1 та Windows 10.
Contango

0

Ще одна додаткова реалізація, яка використовує бінарний JSON у форматі Newtonsoft.Json і не потребує позначення всього атрибутом [Serializable]. Лише один недолік полягає в тому, що об’єкт повинен бути загорнутий в анонімний клас, тому байтовий масив, отриманий при двійковій серіалізації, може відрізнятися від цього.

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

Анонімний клас використовується тому, що BSON повинен починатися з класу або масиву. Я не намагався десаріалізувати байт [] назад до об’єкта і не впевнений, що він працює, але протестував швидкість перетворення в байт [], і це повністю задовольняє мої потреби.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.