Convierta cualquier objeto en un byte []

138

Estoy escribiendo un prototipo de conexión TCP y tengo problemas para homogeneizar los datos que se enviarán.

Por el momento, no estoy enviando nada más que cadenas, pero en el futuro queremos poder enviar cualquier objeto.

El código es bastante simple en este momento, porque pensé que todo podría convertirse en una 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[]'.
  ...
}

Por supuesto, esto se resuelve fácilmente con un

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

El problema es que, si lo hago de esa manera, necesito verificar CADA tipo de objeto que no se pueda convertir a un byte [] en tiempo de ejecución.

Como no conozco todos los objetos que no se pueden convertir en un byte [] en tiempo de ejecución, esto realmente no es una opción.

¿Cómo se convierte un objeto en una matriz de bytes en C # .NET 4.0?

Steve H.
fuente
2
Esto no es posible de ninguna manera significativa en general (considere, por ejemplo, una instancia FileStreamo cualquier objeto que encapsule un identificador como ese).
Jason
2
¿Pretendes que todos los clientes ejecuten .NET? Si la respuesta es no, debe considerar alguna otra forma de serialización (XML, JSON o similares)
R. Martinho Fernandes

Respuestas:

195

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

Tenga en cuenta que objy todas las propiedades / campos dentro obj(y así sucesivamente para todas sus propiedades / campos) deberán etiquetarse con el Serializableatributo para ser serializados con éxito con esto.

Daniel DiPaolo
fuente
13
Tenga cuidado con lo que hace con "cualquier" objeto en el otro lado, ya que puede que ya no tenga sentido (por ejemplo, si ese objeto fuera un identificador de un archivo o similar)
Rowland Shaw
1
Sí, se aplican advertencias normales, pero no es una mala idea recordarles a las personas.
Daniel DiPaolo
24
Puede ser una buena idea envolver el uso de MemoryStream en un usingbloque, ya que liberará con entusiasmo el búfer interno utilizado.
R. Martinho Fernandes
1
¿Este método está vinculado a .NET? ¿Puedo serializar una estructura C con StructLayoutAtrribute y enviarla mediante un socket a un código C y esperar que el código C comprenda la estructura? ¿Supongo que no?
Joe
103

consulte este artículo: http://www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

Usa el siguiente código

// 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
fuente
10
Como se menciona en un comentario a esta respuesta , el MemorySteamdebe estar envuelto en un usingbloque.
rookie1024
¿Hay algo que deba respetar además? Lo implementé de esa manera y formatear un objeto que contiene 3 miembros públicos int32 da como resultado un ByteArray de 244 bytes. ¿No sé algo sobre la sintaxis de C # o hay algo que probablemente extrañaría usar?
dhein
Lo siento, no puedo entender tu problema. ¿Puedes publicar el código?
kombsh
@kombsh Intento en forma corta: [Serializable] clase GameConfiguration {public map_options_t enumMapIndex; público Int32 iPlayerAmount; Int32 iGameID privado; } byte [] baPacket; GameConfiguration objGameConfClient = new GameConfiguration (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); Ahora baPacket contiene alrededor de 244 bytes de contenido f. JSUT esperaba 12.
Dhein
1
@kombsh puede disponer explícitamente de objetos desechables en su ejemplo.
Rudolf Dvoracek
30

Como otros han dicho antes, podría usar la serialización binaria, pero puede producir bytes adicionales o deserializarse en objetos con datos no exactamente iguales. Usar la reflexión por otro lado es bastante complicado y muy lento. Hay otra solución que puede convertir estrictamente sus objetos a bytes y viceversa: la clasificación:

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

Y para convertir bytes en 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);

Es notablemente más lento y en parte inseguro utilizar este enfoque para objetos pequeños y estructuras en comparación con su propio campo de serialización por campo (debido a la doble copia desde / a la memoria no administrada), pero es la forma más fácil de convertir estrictamente el objeto en byte [] sin implementar la serialización y sin atributo [Serializable].

Aberro
fuente
1
¿Por qué crees que StructureToPtr+ Copyes lento? ¿Cómo puede ser más lento que la serialización? ¿Hay alguna solución más rápida?
Anton Samsonov
Si lo usa para estructuras pequeñas que consisten en pocos tipos simples, sí (que es un caso bastante común), es lento debido a la clasificación y la copia cuádruple (de objeto a montón, de montón a bytes, de bytes a montón, de montón) al objeto). Podría ser más rápido cuando se usa IntPtr en lugar de bytes, pero no en este caso. Y es más rápido para estos tipos escribir un serializador propio que simplemente ponga valores en una matriz de bytes. No digo que sea más lento que la serialización integrada ni que sea "muy, muy lento".
Aberro
1
Me gusta este método ya que asigna byte por byte. Este es un método realmente bueno para intercambiar memoria con mapeo C ++. +1 para ti
Hao Nguyen
2
Nota para los usuarios potenciales, aunque es muy inteligente, esta respuesta no funciona en matrices de estructura, objetos que no se pueden ordenar como una estructura no administrada u objetos que tienen un padre ComVisible (falso) en su jerarquía.
TernaryTopiary
1
¿Para deserilizar cómo obtuviste el "tamaño"? envar bytes = new byte[size];
Ricardo
13

Lo que estás buscando es la serialización. Hay varias formas de serialización disponibles para la plataforma .Net

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

Puedes usarlo como el siguiente 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
fuente
6

Usar Encoding.UTF8.GetByteses más rápido que usar MemoryStream. Aquí, estoy usando NewtonsoftJson para convertir el objeto de entrada a una cadena JSON y luego obtengo bytes de la cadena JSON.

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

Punto de referencia para la versión de @Daniel DiPaolo con esta versión

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

Clase de soluciones combinadas en extensiones:

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

}
Error 404
fuente
1

Puede utilizar las herramientas de serialización integradas en el marco y serializar en un MemoryStream . Esta puede ser la opción más sencilla, pero puede producir un byte más grande [] que el estrictamente necesario para su escenario.

Si ese es el caso, podría utilizar la reflexión para iterar sobre los campos y / o propiedades en el objeto a serializar y escribirlos manualmente en el MemoryStream, llamando a la serialización de forma recursiva si es necesario para serializar tipos no triviales. Este método es más complejo y llevará más tiempo implementarlo, pero le permite mucho más control sobre la secuencia serializada.


fuente
1

¿Qué tal algo simple como esto?

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

Prefiero usar la expresión "serialización" que "convertir en bytes". Serializar un objeto significa convertirlo en una matriz de bytes (o XML u otra cosa) que se pueda usar en el cuadro remoto para reconstruir el objeto. En .NET, el Serializableatributo marca tipos cuyos objetos se pueden serializar.

Matthias Meid
fuente
1

Manera alternativa de convertir objeto a matriz de bytes:

TypeConverter objConverter = TypeDescriptor.GetConverter(objMsg.GetType());
byte[] data = (byte[])objConverter.ConvertTo(objMsg, typeof(byte[]));
Khoa Nguyen
fuente
Intenté esto, no parecía funcionar para mí en .NET 4.6.1 y Windows 10.
Contango
0

Una implementación adicional, que utiliza JSON binario Newtonsoft.Json y no requiere marcar todo con el atributo [Serializable]. Solo un inconveniente es que un objeto tiene que estar envuelto en una clase anónima, por lo que la matriz de bytes obtenida con la serialización binaria puede ser diferente de esta.

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

La clase anónima se usa porque BSON debe comenzar con una clase o matriz. No he intentado deserializar el byte [] de vuelta al objeto y no estoy seguro de si funciona, pero he probado la velocidad de conversión al byte [] y satisface completamente mis necesidades.

prime_z
fuente
-2

¿Qué tal la serialización? Echa un vistazo aquí .

Itay Karo
fuente