Конвертировать любой объект в байт []

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

Это, конечно, достаточно легко решить с помощью

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

Проблема в том, что если я делаю это таким образом, мне нужно проверять КАЖДЫЙ тип объекта, который не может быть приведен к байту [] во время выполнения.

Поскольку я не знаю каждый объект, который нельзя преобразовать в byte [] во время выполнения, это действительно не вариант.

Как вообще преобразовать любой объект в массив байтов в C # .NET 4.0?

Стив Х.
источник
2
В общем случае это невозможно каким-либо значимым способом (например, рассмотрим экземпляр FileStreamили любой объект, который инкапсулирует дескриптор подобного типа).
Джейсон
2
Вы хотите, чтобы все клиенты работали с .NET? Если ответ «нет», вы должны рассмотреть какую-то другую форму сериализации (XML, JSON или тому подобное)
Р. Мартиньо Фернандес

Ответы:

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
Будьте осторожны с тем, что вы делаете с «любым» объектом на другой стороне, поскольку это может больше не иметь смысла (например, если этот объект был дескриптором файла или подобным)
Роуланд Шоу,
1
Да, нормальные предостережения применяются, но это не плохая идея, чтобы напомнить людям о них.
Даниэль ДиПаоло
24
Это может быть хорошей идеей, чтобы обернуть использование MemoryStream в usingблоке, так как он освободит внутренний используемый буфер.
Р. Мартиньо Фернандес
1
Этот метод .NET связан? Могу ли я сериализовать структуру C с помощью StructLayoutAtrribute и отправить через сокет в код C и ожидать, что код 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;
}
kombsh
источник
10
Как уже упоминалось в комментарии к этому ответу , они MemorySteamдолжны быть заключены в usingблок.
rookie1024
Есть ли что-то, что я должен уважать при поступлении? Я реализовал это таким образом, и форматирование объекта, содержащего 3 открытых члена int32, приводит к 244-байтовому массиву ByteArray. Я что-то не знаю о синтаксисе C # или есть что-то, что я, вероятно, пропустил бы при использовании?
dhein
Извини, я не могу понять твою проблему. Вы можете опубликовать код?
Комбш
@kombsh Я пытаюсь в краткой форме: [Serializable] class GameConfiguration {public map_options_t enumMapIndex; public Int32 iPlayerAmount; частный Int32 iGameID; } byte [] baPacket; GameConfiguration objGameConfClient = new GameConfiguration (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); Теперь baPacket содержит около 244 байт для содержимого. Я ожидал этого нужно просто 12.
dhein
1
@kombsh вы можете явно утилизировать одноразовые предметы в вашем примере.
Рудольф Дворачек
31

Как уже говорили другие, вы можете использовать двоичную сериализацию, но она может создать дополнительные байты или быть десериализована в объекты с не совсем такими же данными. Использование отражения с другой стороны довольно сложно и очень медленно. Есть еще одно решение, которое может строго преобразовывать ваши объекты в байты и наоборот - сортировка:

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

Заметно медленнее и отчасти небезопасно использовать этот подход для небольших объектов и структур по сравнению с вашим собственным полем сериализации по полю (из-за двойного копирования из / в неуправляемую память), но это самый простой способ строго преобразовать объект в byte [] без реализации сериализации и без атрибута [Сериализуемый].

Aberro
источник
1
Как вы думаете, почему StructureToPtr+ Copyмедленно? Как это может быть медленнее, чем сериализация? Есть ли более быстрое решение?
Антон Самсонов
Если вы используете его для небольших структур, состоящих из нескольких простых типов, да (что является довольно распространенным случаем), это происходит медленно из-за сортировки и четырехкратного копирования (из объекта в кучу, из кучи в байты, из байтов в кучу, из кучи на объект). Это может быть быстрее, если вместо байтов используется IntPtr, но не в этом случае. И для таких типов быстрее написать собственный сериализатор, который просто помещает значения в байтовый массив. Я не говорю, что это медленнее, чем встроенная сериализация, или что это «чертовски медленно».
Аберро
1
Мне нравится этот метод, поскольку он отображает побайтово. Это действительно хороший способ обмена памятью с отображением C ++. +1 для вас.
Хао Нгуен
2
Примечание для потенциальных пользователей, хотя и очень умный, этот ответ не работает с массивами структур, объектами, которые нельзя маршалировать как неуправляемую структуру, или объектами, у которых в иерархии есть родительский элемент ComVisible (false).
TernaryTopiary
1
Чтобы опустошить, как вы получили «размер»? вvar bytes = new byte[size];
Рикардо
13

То, что вы ищете, это сериализация. Для платформы .Net доступно несколько форм сериализации.

JaredPar
источник
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);
   }

}
Ошибка 404
источник
1

Вы можете использовать встроенные средства сериализации в платформе и сериализовать в MemoryStream . Это может быть самый простой вариант, но он может дать больший byte [], чем может быть строго необходим для вашего сценария.

Если это так, вы можете использовать отражение для перебора полей и / или свойств в объекте, который нужно сериализовать, и вручную записать их в MemoryStream, рекурсивно вызывая сериализацию, если это необходимо для сериализации нетривиальных типов. Этот метод более сложен и требует больше времени для реализации, но дает вам гораздо больше контроля над сериализованным потоком.


источник
1

Как насчет чего-нибудь такого простого?

return ((object[])value).Cast<byte>().ToArray(); 
Петр Козак
источник
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
контанго
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 должен начинаться с класса или массива. Я не пробовал десериализовать byte [] обратно в объект и не уверен, работает ли он, но протестировал скорость преобразования в byte [], и это полностью удовлетворяет мои потребности.

prime_z
источник