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


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

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 [] без реализации сериализации и без атрибута [Сериализуемый].


1
Как вы думаете, почему StructureToPtr+ Copyмедленно? Как это может быть медленнее, чем сериализация? Есть ли более быстрое решение?
— Антон Самсонов

Если вы используете его для небольших структур, состоящих из нескольких простых типов, да (что является довольно распространенным случаем), это происходит медленно из-за сортировки и четырехкратного копирования (из объекта в кучу, из кучи в байты, из байтов в кучу, из кучи на объект). Это может быть быстрее, если вместо байтов используется IntPtr, но не в этом случае. И для таких типов быстрее написать собственный сериализатор, который просто помещает значения в байтовый массив. Я не говорю, что это медленнее, чем встроенная сериализация, или что это «чертовски медленно».
— Аберро

1
Мне нравится этот метод, поскольку он отображает побайтово. Это действительно хороший способ обмена памятью с отображением C ++. +1 для вас.
— Хао Нгуен

2
Примечание для потенциальных пользователей, хотя и очень умный, этот ответ не работает с массивами структур, объектами, которые нельзя маршалировать как неуправляемую структуру, или объектами, у которых в иерархии есть родительский элемент ComVisible (false).
— 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 . Это может быть самый простой вариант, но он может дать больший byte [], чем может быть строго необходим для вашего сценария.

Если это так, вы можете использовать отражение для перебора полей и / или свойств в объекте, который нужно сериализовать, и вручную записать их в 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
— контанго

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 [], и это полностью удовлетворяет мои потребности.


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.