Как преобразовать объект в байтовый массив в C #


102

У меня есть набор объектов, которые мне нужно записать в двоичный файл.

Мне нужно, чтобы байты в файле были компактными, поэтому я не могу их использовать BinaryFormatter. BinaryFormatterдобавляет всевозможную информацию для нужд десериализации.

Если я попробую

byte[] myBytes = (byte[]) myObject 

Я получаю исключение во время выполнения.

Мне нужно, чтобы это было быстро, поэтому я бы предпочел не копировать массивы байтов. Я просто хочу, чтобы актеры byte[] myBytes = (byte[]) myObjectпоработали!

Хорошо, чтобы было ясно, у меня не может быть никаких метаданных в выходном файле. Просто байты объекта. Упакованный объект-объект. Судя по полученным ответам, похоже, что я буду писать низкоуровневый Buffer.BlockCopyкод. Возможно, используется небезопасный код.

Ответы:


180

Чтобы преобразовать объект в байтовый массив:

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

Вам просто нужно скопировать эту функцию в свой код и отправить ей объект, который нужно преобразовать в массив байтов. Если вам нужно снова преобразовать массив байтов в объект, вы можете использовать функцию ниже:

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

Вы можете использовать эти функции с пользовательскими классами. Вам просто нужно добавить [Serializable]атрибут в свой класс, чтобы включить сериализацию


9
Я попробовал это, и он добавил всевозможные метаданные. ОП сказал, что ему не нужны метаданные.
— user316117

4
Не говоря уже о том, что все, кажется, предполагают, что то, что вы пытаетесь сериализовать, - это то, что вы написали или уже предварительно настроили для сериализации.
— Hexum064

3
Вы можете передать массив байтов непосредственно в конструктор MemoryStreamво втором примере кода. Это исключило бы использование Write(...)и Seek(...).
— unknown6656 04

41

Если вы хотите, чтобы сериализованные данные были действительно компактными, вы можете сами написать методы сериализации. Таким образом у вас будет минимум накладных расходов.

Пример:

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

}

что у меня есть несколько int для записи и несколько строк?
— Смит

1
@Smith: Да, вы можете это сделать, просто напишите их друг за другом. Они BinaryWriterбудут записывать их в формате, BinaryReaderдоступном для чтения, если вы пишете и читаете их в одном и том же порядке.
— Guffa

1
в чем разница между BinaryWriter/Readerи использованиемBinaryFormatter
— Смит

3
@Smith: Используя, BinaryWriter/Readerвы сами выполняете сериализацию / десериализацию, и вы можете писать / читать только те данные, которые абсолютно необходимы, как можно компактнее. Он BinaryFormatterиспользует отражение, чтобы узнать, какие данные писать / читать, и использует формат, который работает для всех возможных случаев. Он также включает метаинформацию о формате в потоке, что увеличивает накладные расходы.
— Guffa

1
@Smith: вы можете привести перечисление к int(или если вы указали какой-либо другой тип в качестве хранилища для перечисления) и записать его. Когда вы его прочитаете, вы можете привести его к типу перечисления.
— Guffa

31

Ну отливка из myObjectк byte[]никогда не будет работать , если вы не получили явное преобразование или , если myObject этоbyte[] . Вам нужна сериализация рамка какого - то. Их много, в том числе Protocol Buffers, который мне близок и дорог. Он довольно «скудный и скупой» как с точки зрения пространства, так и времени.

Однако вы обнаружите, что почти все фреймворки сериализации имеют значительные ограничения на то, что вы можете сериализовать - буферы протоколов больше, чем некоторые, из-за кросс-платформенности.

Если вы можете указать больше требований, мы сможем помочь вам больше - но это никогда не будет так просто, как кастинг ...

РЕДАКТИРОВАТЬ: просто чтобы ответить на это:

Мне нужен мой двоичный файл, чтобы содержать байты объекта. Только байты, никаких метаданных. Упакованный объект-объект. Поэтому я буду реализовывать настраиваемую сериализацию.

Помните, что байты в ваших объектах довольно часто являются ссылками ... поэтому вам нужно решить, что с ними делать.

Я подозреваю, что вы обнаружите, что разработать и реализовать собственную структуру сериализации сложнее, чем вы думаете.

Я бы лично порекомендовал, если вам нужно сделать это только для нескольких конкретных типов, вы не пытаетесь придумать общую структуру сериализации. Просто реализуйте метод экземпляра и статический метод для всех необходимых вам типов:

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

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


Мне более правильно называть это «protobuf-csharp-port» (Google-code) или «dotnet-protobufs» (Git)?
— Marc Gravell

1
Мне нужен мой двоичный файл, чтобы содержать байты объекта. Только байты, никаких метаданных. Упакованный объект-объект. Поэтому я буду реализовывать настраиваемую сериализацию.
— chuckhlogan

6
Риск нулевых метаданных состоит в том, что в этом случае вы будете очень нетерпимы к версии, поскольку у них очень мало способов обеспечить гибкость, пока не стало слишком поздно. Буферы протокола довольно загружены данными. Вам действительно нужен лишний поворот винта?
— Марк Гравелл

@Marc: И, конечно, для целых чисел PB может оказаться плотнее, чем необработанные байты ...
— Джон Скит

16

Я взял ответ Crystalonics и превратил их в методы расширения. Надеюсь, кому-то они пригодятся:

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
Это действительно полезно и просто !! Спасибо.
— MrHIDEn

13

Вы действительно говорите о сериализации, которая может принимать разные формы. Поскольку вам нужны небольшие и двоичные файлы, буферы протоколов могут быть жизнеспособным вариантом, обеспечивающим также устойчивость к версиям и переносимость. В отличие от этого BinaryFormatter, формат проводных буферов протокола не включает все метаданные типа; просто очень краткие маркеры для идентификации данных.

В .NET есть несколько реализаций; особенно

Я смиренно утверждаю, что protobuf-net (который я написал) допускает большее количество .NET-идиоматического использования с типичными классами C # («обычные» буферы протоколов обычно требуют генерации кода); например:

[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
Даже «краткие маркеры» остаются метаданными. Мое понимание того, что хотел OP, было не чем иным, как данными в объекте. Так, например, если объект был структурой с 2 ​​32-битными целыми числами, то он ожидал бы, что результатом будет массив байтов из 8 байтов.
— user316117

@ user316117, что является настоящей проблемой для управления версиями. У каждого подхода есть свои преимущества и недостатки.
— Marc Gravell


Есть ли способ избежать использования атрибутов Proto *? Сущности, которые я хочу использовать, находятся в сторонней библиотеке.
— Alex 75

5

Это сработало для меня:

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

foo - это объект, в котором я на 100% уверен, что это массив байтов.


2

Взгляните на сериализацию , метод «преобразования» всего объекта в поток байтов. Вы можете отправить его в сеть или записать в файл, а затем восстановить его обратно в объект позже.


Я думаю, что chuckhlogan явно отказался от этого (Formatter == Serialization).
— Хенк Холтерман,

@Henk - смотря какие причины ; он упомянул дополнительную информацию, которую я считаю метаданными типа и информацией о поле; вы можете использовать сериализацию без этих накладных расходов; просто не с BinaryFormatter.
— Марк Грейвелл

2

Я нашел другой способ преобразовать объект в byte [], вот мое решение:

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

С уважением


1

Чтобы получить доступ к памяти объекта напрямую (чтобы сделать «дамп ядра»), вам нужно обратиться к небезопасному коду.

Если вы хотите что-то более компактное, чем BinaryWriter, или необработанный дамп памяти, то вам нужно написать некоторый настраиваемый код сериализации, который извлекает критическую информацию из объекта и упаковывает ее оптимальным образом.

изменить PS Очень легко обернуть подход BinaryWriter в DeflateStream для сжатия данных, что обычно примерно вдвое уменьшает размер данных.


1
Небезопасного кода недостаточно. C # и CLR по-прежнему не позволят вам взять необработанный указатель на управляемый объект даже в небезопасном коде или поместить две ссылки на объекты в объединение.
— Павел Минаев

1

Я считаю, что то, что вы пытаетесь сделать, невозможно.

Создаваемый мусор BinaryFormatterнеобходим для восстановления объекта из файла после остановки вашей программы.
Однако получить данные об объекте можно, вам просто нужно знать его точный размер (сложнее, чем кажется):

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

это можно восстановить с помощью:

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

Причина, по которой вышеуказанные методы не работают для сериализации, заключается в том, что первые четыре байта возвращаемых данных соответствуют файлу RuntimeTypeHandle. RuntimeTypeHandleОписывает макет / тип объекта , но значение его меняется каждый раз , когда программа побежал.

РЕДАКТИРОВАТЬ: это глупо, не делайте этого -> Если вы уже знаете тип объекта, который нужно десериализовать, вы можете переключать эти байты BitConvertes.GetBytes((int)typeof(yourtype).TypeHandle.Value)во время десериализации.

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