Converter qualquer objeto em um byte []

138

Estou escrevendo uma conexão TCP de protótipo e estou tendo problemas para homogeneizar os dados a serem enviados.

No momento, estou enviando nada além de seqüências de caracteres, mas no futuro queremos poder enviar qualquer objeto.

O código é bastante simples no momento, porque eu pensei que tudo poderia ser convertido em uma matriz de bytes:

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

É claro que isso é facilmente resolvido com um

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

O problema é que, se eu fizer dessa maneira, preciso verificar TODOS os tipos de objetos que não podem ser convertidos em um byte [] em tempo de execução.

Como eu não conheço todos os objetos que não podem ser convertidos em um byte [] em tempo de execução, isso realmente não é uma opção.

Como alguém converte qualquer objeto em uma matriz de bytes no C # .NET 4.0?

Steve H.
fonte
2
Isso não é possível de maneira significativa em geral (considere, por exemplo, uma instância de FileStreamou qualquer objeto que encapsule um identificador como esse).
Jason
2
Você pretende ter todos os clientes executando o .NET? Se a resposta é não, você deve considerar alguma outra forma de serialização (XML, JSON, ou os gostos)
R. Martinho Fernandes

Respostas:

195

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

Observe que objtodas as propriedades / campos obj(e assim por diante para todas as suas propriedades / campos) precisarão ser marcados com o Serializableatributo para serem serializados com êxito.

Daniel DiPaolo
fonte
13
Tenha cuidado com o que você faz com "qualquer" objeto do outro lado, pois pode não fazer mais sentido (por exemplo, se esse objeto fosse um identificador para um arquivo ou semelhante)
Rowland Shaw
1
Sim, aplicam-se advertências normais, mas não é uma má idéia lembrá-las.
Daniel DiPaolo
24
Pode ser uma boa idéia agrupar o uso do MemoryStream em um usingbloco, pois ele libera ansiosamente o buffer interno usado.
R. Martinho Fernandes
1
Esse método é .NET vinculado? Posso serializar uma estrutura C com StructLayoutAtrribute e enviar via soquete para um código C e esperar que o código C entenda a estrutura? Eu acho que não?
joe
103

confira este artigo: http://www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

Use o código abaixo

// 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
fonte
10
Como mencionado em um comentário a esta resposta , o MemorySteampacote deve ser embrulhado em um usingbloco.
rookie1024
existe algo que eu tenho que respeitar em adição? Eu implementei dessa maneira e Formatar um objeto contendo 3 membros públicos int32 resulta em um ByteArray de 244 bytes. Não estou sabendo algo sobre a sintaxe do C # ou há algo que provavelmente sentiria falta de usar?
dhein
Desculpe, não consigo resolver o seu problema. Você pode postar o código?
Kombsh #
@kombsh Eu tento de forma curta: classe [Serializable] GameConfiguration {public map_options_t enumMapIndex; public Int32 iPlayerAmount; privado Int32 iGameID; byte [] baPacket; GameConfiguration objGameConfClient = new GameConfiguration (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); Agora, o baPacket contém cerca de 244 bytes de conteúdo. Eu apenas esperava 12.
dhein
1
@kombsh, você pode descartar explicitamente objetos descartáveis ​​no seu exemplo.
Rudolf Dvoracek
30

Como outros já disseram, você pode usar a serialização binária, mas pode produzir bytes extras ou ser desserializado em objetos com dados não exatamente iguais. Usar a reflexão, por outro lado, é bastante complicado e muito lento. Existe uma outra solução que pode converter estritamente seus objetos em bytes e 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);

E para converter bytes em objeto:

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

É visivelmente mais lento e parcialmente inseguro usar essa abordagem para objetos e estruturas pequenos, comparando seu próprio campo de serialização por campo (devido à cópia dupla de / para memória não gerenciada), mas é a maneira mais fácil de converter estritamente objeto em byte [] sem implementar serialização e sem o atributo [Serializable].

Aberro
fonte
1
Por que você acha que o StructureToPtr+ Copyé lento? Como pode ser mais lento que a serialização? Existe alguma solução mais rápida?
Anton Samsonov 31/10
Se você usá-lo para pequenas estruturas que consistem em poucos tipos simples, sim (o que é um caso bastante comum), é lento por causa do empacotamento e cópia em quad (do objeto para a pilha, da pilha para bytes, dos bytes para pilha, da pilha objetar). Pode ser mais rápido quando o IntPtr é usado em vez de bytes, mas não neste caso. E é mais rápido para esses tipos escreverem o próprio serializador que simplesmente coloca valores na matriz de bytes. Não estou dizendo que é mais lento que a serialização incorporada, nem que é "muito lento".
Aberro
1
Eu gosto desse método, pois mapeia byte a byte. Este é realmente um bom método para trocar memória com o mapeamento C ++. +1 para você.
Hao Nguyen
2
Observe que, para os usuários em potencial, apesar de muito inteligentes, essa resposta não funciona em matrizes de estrutura, objetos que não podem ser empacotados como uma estrutura não gerenciada ou objetos que possuem um pai ComVisible (falso) em sua hierarquia.
TernaryTopiary
1
Deserilizar como você obteve o "tamanho"? emvar bytes = new byte[size];
Ricardo
13

O que você está procurando é serialização. Existem várias formas de serialização disponíveis para a plataforma .Net

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

Você pode usá-lo como abaixo do código.

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
fonte
6

Usar Encoding.UTF8.GetBytesé mais rápido que usar MemoryStream. Aqui, estou usando o NewtonsoftJson para converter o objeto de entrada em string JSON e, em seguida, obtendo bytes da string JSON.

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

Referência para a versão de @Daniel DiPaolo com esta versão

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
fonte
2

Classe Soluções combinadas em extensões:

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

}
Erro 404
fonte
1

Você pode usar as ferramentas internas de serialização na estrutura e serializar para um MemoryStream . Essa pode ser a opção mais direta, mas pode produzir um byte maior [] do que o estritamente necessário para o seu cenário.

Se for esse o caso, você pode utilizar a reflexão para iterar sobre os campos e / ou propriedades no objeto a ser serializado e gravá-los manualmente no MemoryStream, chamando a serialização recursivamente, se necessário, para serializar tipos não triviais. Esse método é mais complexo e levará mais tempo para implementar, mas permite muito mais controle sobre o fluxo serializado.


fonte
1

Que tal algo simples assim?

return ((object[])value).Cast<byte>().ToArray(); 
Peter Kozak
fonte
1

Prefiro usar a expressão "serialização" do que "converter em bytes". Serializar um objeto significa convertê-lo em uma matriz de bytes (ou XML ou outra coisa) que pode ser usada na caixa remota para reconstruir o objeto. No .NET, o Serializableatributo marca os tipos cujos objetos podem ser serializados.

Matthias Meid
fonte
1

Maneira alternativa de converter objeto em matriz de bytes:

TypeConverter objConverter = TypeDescriptor.GetConverter(objMsg.GetType());
byte[] data = (byte[])objConverter.ConvertTo(objMsg, typeof(byte[]));
Khoa Nguyen
fonte
Tentei isso, não pareceu funcionar para mim no .NET 4.6.1 e Windows 10.
Contango
0

Uma implementação adicional, que usa o JSON binário Newtonsoft.Json e não exige a marcação de tudo com o atributo [Serializable]. Apenas uma desvantagem é que um objeto precisa ser agrupado em uma classe anônima, portanto a matriz de bytes obtida com a serialização binária pode ser diferente desta.

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

A classe anônima é usada porque o BSON deve começar com uma classe ou matriz. Eu não tentei desserializar o byte [] de volta ao objeto e não tenho certeza se ele funciona, mas testei a velocidade da conversão em byte [] e satisfaz completamente minhas necessidades.

prime_z
fonte
-2

E a serialização? dê uma olhada aqui .

Itay Karo
fonte