Как преобразовать объект в байтовый массив в C # - PullRequest
76 голосов
/ 18 сентября 2009

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

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

Если я попытаюсь

byte[] myBytes = (byte[]) myObject 

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

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

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

Ответы [ 11 ]

137 голосов
/ 08 мая 2012

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

// 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] в ваш класс, чтобы включить сериализацию

34 голосов
/ 19 сентября 2009

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

Пример:

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

}
29 голосов
/ 19 сентября 2009

Хорошо, приведение от myObject до byte[] никогда не сработает, если у вас нет явного преобразования или если myObject равно a byte[]. Вам нужна структура сериализации некоторого вида. Там много всего, в том числе буфер протокола , который мне близок и дорог. Это довольно "скудно и подло" с точки зрения пространства и времени.

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

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

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

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

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

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

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

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

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

12 голосов
/ 19 сентября 2009

Вы действительно говорите о сериализации, которая может принимать разные формы. Так как вам нужен маленький и двоичный файлы, буферы протокола могут быть жизнеспособным вариантом - они также дают толерантность к версии и переносимость. В отличие от 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);
11 голосов
/ 25 января 2014

Я взял ответ 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;
    }
}
2 голосов
/ 25 апреля 2015

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

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

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

2 голосов
/ 19 сентября 2009

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

1 голос
/ 18 июля 2015

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

Мусор, создаваемый 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) во время десериализации.

1 голос
/ 15 июля 2014

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

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

Привет

1 голос
/ 19 сентября 2009

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...