Convertit n'importe quel objet en octet []

138

J'écris un prototype de connexion TCP et j'ai du mal à homogénéiser les données à envoyer.

Pour le moment, je n'envoie que des chaînes, mais à l'avenir nous voulons pouvoir envoyer n'importe quel objet.

Le code est assez simple pour le moment, car je pensais que tout pouvait être converti dans un tableau d'octets:

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

Ceci est bien sûr assez facilement résolu avec un

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

Le problème est que si je le fais de cette façon, je dois vérifier CHAQUE type d'objet qui ne peut pas être converti en octet [] lors de l'exécution.

Puisque je ne connais pas tous les objets qui ne peuvent pas être convertis en octet [] à l'exécution, ce n'est vraiment pas une option.

Comment convertir n'importe quel objet en un tableau d'octets en C # .NET 4.0?

Steve H.
la source
2
Ce n'est pas possible de manière significative en général (considérez, par exemple, une instance de FileStream, ou tout objet qui encapsule un handle comme celui-ci).
jason
2
Avez-vous l'intention que tous les clients exécutent .NET? Si la réponse est non, vous devriez envisager une autre forme de sérialisation (XML, JSON ou autres)
R. Martinho Fernandes

Réponses:

195

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

Notez que objtoutes les propriétés / champs dans obj(et ainsi de suite pour toutes leurs propriétés / champs) devront tous être étiquetés avec l' Serializableattribut pour être sérialisés avec cela.

Daniel DiPaolo
la source
13
Soyez prudent avec ce que vous faites avec "n'importe quel" objet de l'autre côté, car cela peut ne plus avoir de sens (par exemple, si cet objet était un handle vers un fichier, ou similaire)
Rowland Shaw
1
Oui, des mises en garde normales s'appliquent, mais ce n'est pas une mauvaise idée de les rappeler aux gens.
Daniel DiPaolo
24
Il peut être judicieux d'encapsuler l'utilisation de MemoryStream dans un usingbloc, car il libèrera avec empressement le tampon interne utilisé.
R. Martinho Fernandes
1
Cette méthode est-elle liée à .NET? Puis-je sérialiser une structure C avec StructLayoutAtrribute et l'envoyer via socket à un code C et m'attendre à ce que le code C comprenne la structure? Je suppose que non?
joe
103

consultez cet article: http://www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

Utilisez le code ci-dessous

// 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
la source
10
Comme mentionné dans un commentaire à cette réponse , le MemorySteamdoit être enveloppé dans un usingbloc.
rookie1024
y a-t-il quelque chose que je dois respecter en plus? Je l'ai implémenté de cette façon et le formatage d'un objet contenant 3 membres publics int32 aboutit à un ByteArray de 244 octets de long. Est-ce que je ne connais pas quelque chose sur la syntaxe C # ou y a-t-il quelque chose que je manquerais probablement d'utiliser?
dhein
Désolé, je ne peux pas comprendre votre problème. Pouvez-vous poster le code?
kombsh
@kombsh J'essaye sous une forme courte: [Serializable] class GameConfiguration {public map_options_t enumMapIndex; public Int32 iPlayerAmount; iGameID Int32 privé; } byte [] baPacket; GameConfiguration objGameConfClient = nouveau GameConfiguration (); baPacket = BinModler.ObjectToByteArray (objGameConfClient); Maintenant baPacket contient environ 244 octets de contenu. J'attendais déjà le 12.
dhein
1
@kombsh, vous pouvez supprimer explicitement les objets jetables dans votre exemple.
Rudolf Dvoracek le
30

Comme d'autres l'ont déjà dit, vous pouvez utiliser la sérialisation binaire, mais cela peut produire des octets supplémentaires ou être désérialisé en objets avec pas exactement les mêmes données. Utiliser la réflexion par contre est assez compliqué et très lent. Il existe une autre solution qui permet de convertir strictement vos objets en octets et vice-versa - le 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);

Et pour convertir des octets en objet:

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

Il est nettement plus lent et en partie dangereux d'utiliser cette approche pour les petits objets et structures par rapport à votre propre champ de sérialisation par champ (en raison de la double copie de / vers la mémoire non gérée), mais c'est le moyen le plus simple de convertir strictement l'objet en octet [] sans implémenter la sérialisation et sans attribut [Serializable].

Aberro
la source
1
Pourquoi pensez-vous que StructureToPtr+ Copyest lent? Comment peut-il être plus lent que la sérialisation? Existe-t-il une solution plus rapide?
Anton Samsonov
Si vous l'utilisez pour de petites structures composées de quelques types simples, oui (ce qui est un cas assez courant), il est lent à cause du marshalling et de la copie quadruple (d'objet en tas, de tas en octets, d'octets en tas, de tas objecter). Cela pourrait être plus rapide lorsque IntPtr est utilisé au lieu d'octets, mais pas dans ce cas. Et il est plus rapide pour ces types d'écrire leur propre sérialiseur qui met simplement des valeurs dans un tableau d'octets. Je ne dis pas que c'est plus lent que la sérialisation intégrée ni que c'est "vraiment très lent".
Aberro
1
J'aime cette méthode car elle mappe octet par octet. C'est une très bonne méthode pour échanger de la mémoire avec le mappage C ++. +1 pour vous.
Hao Nguyen
2
Remarque aux utilisateurs potentiels, bien que très intelligente, cette réponse ne fonctionne pas sur les tableaux de structure, les objets qui ne peuvent pas être marshalés en tant que structure non gérée ou les objets qui ont un parent ComVisible (faux) dans leur hiérarchie.
TernaryTopiary
1
Pour désériliser comment vous avez obtenu la "taille"? dansvar bytes = new byte[size];
Ricardo
13

Ce que vous recherchez, c'est la sérialisation. Il existe plusieurs formes de sérialisation disponibles pour la plateforme .Net

JaredPar
la source
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;
    }
}

Vous pouvez l'utiliser comme le code ci-dessous.

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 jeu.
la source
6

L'utilisation Encoding.UTF8.GetBytesest plus rapide que l'utilisation MemoryStream. Ici, j'utilise NewtonsoftJson pour convertir l'objet d'entrée en chaîne JSON, puis j'obtiens les octets de la chaîne JSON.

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

Benchmark pour la version de @Daniel DiPaolo avec cette version

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
la source
2

Solutions combinées dans la classe 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);
   }

}
Erreur 404
la source
1

Vous pouvez utiliser les outils de sérialisation intégrés dans le framework et sérialiser vers un MemoryStream . Cela peut être l'option la plus simple, mais peut produire un octet [] plus grand que ce qui peut être strictement nécessaire pour votre scénario.

Si tel est le cas, vous pouvez utiliser la réflexion pour parcourir les champs et / ou propriétés de l'objet à sérialiser et les écrire manuellement dans le MemoryStream, en appelant la sérialisation de manière récursive si nécessaire pour sérialiser des types non triviaux. Cette méthode est plus complexe et prendra plus de temps à implémenter, mais vous permet de mieux contrôler le flux sérialisé.


la source
1

Que diriez-vous de quelque chose de simple comme ça?

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

Je préfère utiliser l'expression «sérialisation» plutôt que «transtypage en octets». Sérialiser un objet signifie le convertir en un tableau d'octets (ou XML, ou autre chose) qui peut être utilisé sur la boîte distante pour reconstruire l'objet. Dans .NET, l' Serializableattribut marque les types dont les objets peuvent être sérialisés.

Matthias Meid
la source
1

Autre façon de convertir un objet en tableau d'octets:

TypeConverter objConverter = TypeDescriptor.GetConverter(objMsg.GetType());
byte[] data = (byte[])objConverter.ConvertTo(objMsg, typeof(byte[]));
Khoa Nguyen
la source
J'ai essayé cela, cela ne semblait pas fonctionner pour moi sur .NET 4.6.1 et Windows 10.
Contango
0

Une implémentation supplémentaire, qui utilise le JSON binaire Newtonsoft.Json et ne nécessite pas de tout marquer avec l'attribut [Serializable]. Un seul inconvénient est qu'un objet doit être enveloppé dans une classe anonyme, donc le tableau d'octets obtenu avec la sérialisation binaire peut être différent de celui-ci.

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 classe anonyme est utilisée car BSON doit commencer par une classe ou un tableau. Je n'ai pas essayé de désérialiser l'octet [] en objet et je ne suis pas sûr que cela fonctionne, mais j'ai testé la vitesse de conversion en octet [] et cela satisfait complètement mes besoins.

prime_z
la source
-2

Et la sérialisation? jetez un oeil ici .

Itay Karo
la source