J'écris un prototype de connexion TCP et je rencontre des problèmes pour 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 pourrait être transposé 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 manière, je dois vérifier CHAQUE type d'objet qui ne peut pas être converti en octet [] au moment de l'exécution.
Étant donné que je ne connais pas tous les objets qui ne peuvent pas être convertis en octet [] au moment de l'exécution, ce n'est vraiment pas une option.
Comment convertir un objet en tableau d'octets dans C # .NET 4.0?
Utilisez la 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 obj
et toutes les propriétés/champs dans obj
(et ainsi de suite pour toutes leurs propriétés/champs) devront tous être marqués avec l'attribut Serializable
pour être sérialisés avec cela.
découvrez 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;
}
Comme d'autres l'ont déjà dit, vous pouvez utiliser la sérialisation binaire, mais elle peut générer des octets supplémentaires ou être désérialisée dans un objet ne contenant pas exactement les mêmes données. En revanche, utiliser la réflexion est assez compliqué et très lent ... Il existe une autre solution permettant de convertir strictement vos objets en octets et inversement - 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 visiblement plus lent et partiellement dangereux d’utiliser cette approche pour les petits objets et les structures comparant votre champ de sérialisation par champ (en raison de la double copie de/vers la mémoire non gérée), mais il s’agit du moyen le plus simple de convertir un objet en octet [] sans mettre en oeuvre la sérialisation et sans attribut [Serializable].
Ce que vous recherchez, c'est la sérialisation. Plusieurs formes de sérialisation sont disponibles pour la plate-forme .Net.
byte[]
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 ci-dessous le code.
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>();
Que diriez-vous de quelque chose de simple comme ça?
return ((object[])value).Cast<byte>().ToArray();
Autre moyen de convertir un objet en tableau d'octets:
TypeConverter objConverter = TypeDescriptor.GetConverter(objMsg.GetType());
byte[] data = (byte[])objConverter.ConvertTo(objMsg, typeof(byte[]));
Je préférerais utiliser l'expression "sérialisation" plutôt que "conversion en octets". Sérialiser un objet signifie le convertir en un tableau d'octets (ou XML, ou autre) pouvant être utilisé sur la zone distante pour reconstruire l'objet. Dans .NET, l'attribut Serializable
marque les types dont les objets peuvent être sérialisés.
Vous pouvez utiliser les outils de sérialisation intégrés dans le cadre et les sérialiser sur un MemoryStream . Cette option peut être la plus simple, mais peut générer un octet [] plus important 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 les propriétés de l'objet à sérialiser et les écrire manuellement dans MemoryStream, en appelant la sérialisation récursivement si nécessaire pour sérialiser des types non triviaux. Cette méthode est plus complexe et prend plus de temps à implémenter, mais vous permet de contrôler beaucoup plus le flux sérialisé.
Solutions combinées en 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);
}
}
Utiliser Encoding.UTF8.GetBytes
est plus rapide que d'utiliser MemoryStram
. Ici, j'utilise NewtonsoftJson pour convertir un objet d'entrée en chaîne JSON, puis obtenir des octets à partir de la chaîne JSON.
byte[] SerializeObject(object value) =>Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value));
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 |
Une implémentation supplémentaire, qui utilise Newtonsoft.Json JSON binaire et ne nécessite pas de tout marquer avec l'attribut [Serializable]. Un seul inconvénient est qu'un objet doit être encapsulé dans une classe anonyme. Le tableau d'octets obtenu avec la sérialisation binaire peut donc ê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 parce que BSON devrait commencer par une classe ou un tableau . Je n'ai pas essayé de désérialiser 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 complètement satisfait mes besoins.