Konwertuj dowolny obiekt na bajt []

138

Piszę prototypowe połączenie TCP i mam problem z ujednoliceniem wysyłanych danych.

W tej chwili wysyłam tylko napisy, ale w przyszłości chcemy mieć możliwość wysyłania dowolnego obiektu.

Kod jest w tej chwili dość prosty, ponieważ myślałem, że wszystko można rzucić na tablicę bajtów:

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

Oczywiście można to łatwo rozwiązać za pomocą pliku

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

Problem polega na tym, że jeśli robię to w ten sposób, muszę sprawdzić KAŻDY typ obiektu, którego nie można rzutować na bajt [] w czasie wykonywania.

Ponieważ nie znam każdego obiektu, którego nie można wrzucić do bajtu [] w czasie wykonywania, tak naprawdę nie ma takiej opcji.

Jak przekonwertować dowolny obiekt na tablicę bajtów w C # .NET 4.0?

Steve H.
źródło
2
Nie jest to ogólnie możliwe w żaden znaczący sposób (rozważ na przykład wystąpienie FileStreamlub dowolny obiekt, który zawiera taki uchwyt).
jason
2
Czy chcesz, aby wszyscy klienci korzystali z platformy .NET? Jeśli odpowiedź brzmi nie, powinieneś rozważyć inną formę serializacji (XML, JSON lub podobne)
R.Martinho Fernandes

Odpowiedzi:

195

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

Zauważ, że obji wszystkie właściwości / pola w obrębie obj(i tak dalej dla wszystkich ich właściwości / pól) będą musiały być oznaczone Serializableatrybutem, aby pomyślnie zserializować z tym.

Daniel DiPaolo
źródło
13
Uważaj na to, co robisz z „jakimkolwiek” obiektem po drugiej stronie, ponieważ może to już nie mieć sensu (na przykład, jeśli ten obiekt był uchwytem do pliku lub podobnym)
Rowland Shaw,
1
Tak, obowiązują normalne zastrzeżenia, ale przypominanie o nich ludziom nie jest złym pomysłem.
Daniel DiPaolo
24
Dobrym pomysłem może być zawinięcie użycia MemoryStream w usingbloku, ponieważ chętnie zwolni on używany wewnętrzny bufor.
R. Martinho Fernandes
1
Czy ta metoda jest związana z platformą .NET? Czy mogę serializować strukturę C za pomocą StructLayoutAtrribute i wysłać przez gniazdo do kodu C i oczekiwać, że kod C zrozumie strukturę? Nie sądzę?
joe
103

sprawdź ten artykuł: http://www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

Użyj poniższego kodu

// 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
źródło
10
Jak wspomniano w komentarzu do tej odpowiedzi , MemorySteampowinien on być umieszczony w usingbloku.
rookie1024
czy jest coś, co muszę dodatkowo uszanować? Zaimplementowałem to w ten sposób, a formatowanie obiektu zawierającego 3 publiczne elementy int32 daje w wyniku ByteArray o długości 244 bajtów. Czy nie wiem coś o składni C #, czy jest coś, czego prawdopodobnie przegapiłbym przy użyciu?
dhein
Przepraszam, nie mam twojego problemu. Czy możesz wysłać kod?
kombsh
@kombsh Próbuję w krótkiej formie: [Serializable] class GameConfiguration {public map_options_t enumMapIndex; public Int32 iPlayerAmount; prywatny Int32 iGameID; } bajt [] baPacket; GameConfiguration objGameConfClient = new GameConfiguration (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); Teraz baPacket zawiera około 244 bajtów zawartości. Spodziewałem się 12.
dhein
1
@kombsh możesz jawnie pozbyć się obiektów jednorazowego użytku w swoim przykładzie.
Rudolf Dvoracek
30

Tak jak inni powiedzieli wcześniej, możesz użyć serializacji binarnej, ale może ona wygenerować dodatkowe bajty lub zostać zdeserializowana do obiektów z nie dokładnie tymi samymi danymi. Z drugiej strony korzystanie z refleksji jest dość skomplikowane i bardzo powolne. Istnieje inne rozwiązanie, które może ściśle konwertować twoje obiekty na bajty i vice versa - marshalling:

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

I przekonwertować bajty na obiekt:

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

Jest to zauważalnie wolniejsze i częściowo niebezpieczne, aby używać tego podejścia do małych obiektów i struktur w porównaniu do własnej serializacji pole po polu (z powodu podwójnego kopiowania z / do niezarządzanej pamięci), ale jest to najłatwiejszy sposób na ścisłą konwersję obiektu na bajt [] bez implementowania serializacji i bez atrybutu [Serializable].

Aberro
źródło
1
Jak myślisz, dlaczego StructureToPtr+ Copyjest powolny? Jak to może być wolniejsze niż serializacja? Czy jest jakieś szybsze rozwiązanie?
Anton Samsonov
Jeśli używasz go do małych struktur składających się z kilku prostych typów, tak (co jest dość powszechnym przypadkiem), jest powolny z powodu marshallingu i kopiowania quad (od obiektu do sterty, od sterty do bajtów, od bajtów do sterty, od sterty sprzeciwiać się). Może być szybsze, gdy zamiast bajtów używany jest IntPtr, ale nie w tym przypadku. Takie typy mogą szybciej napisać własny serializator, który po prostu umieszcza wartości w tablicy bajtów. Nie mówię, że jest wolniejszy niż serializacja wbudowana ani że jest „tak cholernie wolny”.
Aberro
1
Podoba mi się ta metoda, ponieważ odwzorowuje bajt po bajcie. To naprawdę dobra metoda wymiany pamięci z mapowaniem C ++. +1 dla Ciebie.
Hao Nguyen
2
Uwaga dla potencjalnych użytkowników, choć bardzo inteligentna, ta odpowiedź nie działa w przypadku tablic struktur, obiektów, których nie można zorganizować jako niezarządzanej struktury lub obiektów, które mają w swojej hierarchii element nadrzędny ComVisible (fałszywy).
TernaryTopiary
1
Aby zdeserilizować, w jaki sposób uzyskałeś „rozmiar”? wvar bytes = new byte[size];
Ricardo
13

To, czego szukasz, to serializacja. Na platformie .Net dostępnych jest kilka form serializacji

JaredPar
źródło
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;
    }
}

Możesz go użyć jak poniżej kodu.

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>();
Frank Myat Thu
źródło
6

Używanie Encoding.UTF8.GetBytesjest szybsze niż używanie MemoryStream. Tutaj używam NewtonsoftJson, aby przekonwertować obiekt wejściowy na ciąg JSON, a następnie pobrać bajty z ciągu JSON.

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

Benchmark dla wersji @Daniel DiPaolo z tą wersją

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 |
kiran
źródło
2

Połączone rozwiązania w klasie Extensions:

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

}
Błąd 404
źródło
1

Można użyć wbudowanych narzędzi serializacji w ramach i serializować do MemoryStream . Może to być najprostsza opcja, ale może generować większy bajt [], niż może to być absolutnie konieczne w Twoim scenariuszu.

W takim przypadku można użyć odbicia do iteracji po polach i / lub właściwościach obiektu, który ma zostać serializowany, i ręcznie zapisać je w MemoryStream, wywołując cykliczne wywołanie serializacji w razie potrzeby w celu serializacji nietrywialnych typów. Ta metoda jest bardziej złożona i jej wdrożenie zajmie więcej czasu, ale umożliwia znacznie większą kontrolę nad serializowanym strumieniem.


źródło
1

A może coś tak prostego?

return ((object[])value).Cast<byte>().ToArray(); 
Peter Kozak
źródło
1

Wolę raczej użyć wyrażenia „serializacja” niż „rzutowanie na bajty”. Serializacja obiektu oznacza konwersję go na tablicę bajtów (lub XML lub coś innego), której można użyć na zdalnym urządzeniu do ponownego skonstruowania obiektu. W .NETSerializable atrybut oznacza typy, których obiekty mogą być serializowane.

Matthias Meid
źródło
1

Alternatywny sposób konwersji obiektu na tablicę bajtów:

TypeConverter objConverter = TypeDescriptor.GetConverter(objMsg.GetType());
byte[] data = (byte[])objConverter.ConvertTo(objMsg, typeof(byte[]));
Khoa Nguyen
źródło
Wypróbowałem to, wydaje mi się, że nie działa na .NET 4.6.1 i Windows 10.
Contango,
0

Jedna dodatkowa implementacja, która korzysta z binarnego formatu JSON Newtonsoft.Json i nie wymaga oznaczania wszystkiego atrybutem [Serializable]. Jedyną wadą jest to, że obiekt musi być opakowany w anonimową klasę, więc tablica bajtów uzyskana za pomocą serializacji binarnej może się różnić od tej.

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

Używana jest klasa anonimowa, ponieważ BSON powinien zaczynać się od klasy lub tablicy. Nie próbowałem deserializować bajtu [] z powrotem do obiektu i nie jestem pewien, czy to działa, ale przetestowałem szybkość konwersji na bajt [] i całkowicie spełnia moje potrzeby.

prime_z
źródło