オブジェクトをバイトに変換します[]


138

プロトタイプTCP接続を作成していますが、送信するデータの均質化に問題があります。

現時点では文字列のみを送信していますが、将来的には任意のオブジェクトを送信できるようにしたいと考えています。

すべてがバイト配列にキャストできると思ったので、コードは現時点では非常に単純です。

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

もちろんこれは、

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

問題は、そのようにすると、実行時にbyte []に​​キャストできないすべてのタイプのオブジェクトをチェックする必要があることです。

実行時にbyte []に​​キャストできないすべてのオブジェクトを知っているわけではないので、これは実際にはオプションではありません。

C#.NET 4.0でオブジェクトをバイト配列に変換するにはどうすればよいですか?


2
これは、一般的に意味のある方法では不可能です(たとえば、のインスタンスFileStream、またはそのようなハンドルをカプセル化するオブジェクトを考えてください)。
ジェイソン

2
すべてのクライアントで.NETを実行するつもりですか?答えが「いいえ」の場合、他の形式のシリアル化(XML、JSONなど)を検討する必要があります
R. Martinho Fernandes

回答:


195

を使用してください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();
    }
}

なお、obj内および任意のプロパティ/フィールドobj(そのプロパティ/フィールドのすべてのためにとそうオン)のすべての必要がでタグ付けされるだろうSerializable属性に成功し、これに直列化されます。


13
反対側にある「任意の」オブジェクトをどう処理するかには注意してください。これは意味をなさなくなる可能性があるためです(たとえば、そのオブジェクトがファイルへのハンドルなどの場合)
Rowland Shaw

1
うん、通常の警告が適用されますが、それらを人々に思い出させることは悪い考えではありません。
Daniel DiPaolo

24
MemoryStreamの使用をusingブロックでラップすることをお勧めします。これは、使用されている内部バッファーを熱心に解放するためです。
R.マルティーニョフェルナンデス

1
このメソッドは.NETバインドですか?StructLayoutAtrributeを使用してC構造体をシリアル化し、ソケットを介してCコードに送信して、Cコードが構造体を理解することを期待できますか?違いますか?
joe

103

この記事をチェックアウト:http : //www.morgantechspace.com/2013/08/convert-object-to-byte-array-and-vice.html

以下のコードを使用してください

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

10
コメントで述べたように、この答えはMemorySteamで包まれるべきusingブロック。
rookie1024 14

それに加えて、尊敬すべき点はありますか?その方法で実装し、3つのint32パブリックメンバーを含むオブジェクトをフォーマットすると、長さが244バイトのByteArrayになります。C#の構文について何か知らないのでしょうか、それとも使用すると見逃す可能性があるものはありますか?
dhein 2014

申し訳ありませんが、問題を解決できません。コードを投稿できますか?
kombsh 2014

@kombsh私は短い形式で試します:[Serializable] class GameConfiguration {public map_options_t enumMapIndex; public Int32 iPlayerAmount; プライベートInt32 iGameID; } byte [] baPacket; GameConfiguration objGameConfClient = new GameConfiguration(); baPacket = BinModler.ObjectToByteArray(objGameConfClient); 現在、baPacketには約244バイトのコンテンツが含まれています。私は12を予想しました
dhein '25

1
@kombshの例では、使い捨てオブジェクトを明示的に破棄できます。
Rudolf Dvoracek 2017

30

他の人が以前に言ったように、バイナリシリアル化を使用できますが、余分なバイトが生成されるか、完全に同じデータではないオブジェクトに逆シリアル化される可能性があります。一方、リフレクションの使用は非常に複雑で非常に低速です。オブジェクトをバイトに厳密に変換したり、逆にマーシャリングしたりできる別のソリューションがあります。

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

そしてバイトをオブジェクトに変換するには:

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

小さなオブジェクトや構造体に対してこのアプローチを使用すると、フィールドごとに独自のシリアル化フィールドと比較して著しく遅くなり、部分的に安全ではありません(アンマネージメモリとの間の二重コピーのため)、シリアル化を実装せずにオブジェクトを厳密にbyte []に​​変換する最も簡単な方法です。 [Serializable]属性なし。


1
StructureToPtr+ Copyが遅いと思うのはなぜですか?シリアライゼーションよりも遅いのですか?より速い解決策はありますか?
アントンサムソノフ2013年

少数の単純な型で構成される小さな構造体に使用する場合、はい(これは非常に一般的なケースです)、マーシャリングとクワッドコピー(オブジェクトからヒープ、ヒープからバイト、バイトからヒープ、ヒープから)のために遅くなります物申す)。バイトの代わりにIntPtrを使用すると高速になる可能性がありますが、この場合は使用できません。そして、そのような型の場合、値をバイト配列に単純に入れる独自のシリアライザーを書く方が高速です。組み込みのシリアライゼーションよりも遅いとか、「とても遅い」とか言っているわけではありません。
Aberro 2013年

1
バイトごとにマップするため、この方法が好きです。これは、C ++マッピングでメモリを交換するための本当に良い方法です。+1してください。
Hao Nguyen、

2
潜在的なユーザーへの注意、非常に賢明ではありますが、この回答は構造体配列、アンマネージ構造体としてマーシャリングできないオブジェクト、または階層にComVisible(false)親を持つオブジェクトでは機能しません。
TernaryTopiary

1
どのようにして「サイズ」を取得したのかを消毒するには?invar bytes = new byte[size];
リカルド


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

以下のコードのように使用できます。

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

6

を使用するEncoding.UTF8.GetBytesよりもを使用した方が高速ですMemoryStream。ここでは、NewtonsoftJsonを使用して入力オブジェクトをJSON文字列に変換し、JSON文字列からバイトを取得しています。

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

このバージョンでの@Daniel DiPaoloのバージョンのベンチマーク

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 |

2

拡張機能クラスの組み合わせソリューション:

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

}

1

フレームワークの組み込みシリアル化ツールを使用して、MemoryStreamにシリアル化できます。これは最も簡単なオプションですが、シナリオに厳密に必要な場合よりも大きなbyte []が生成される可能性があります。

その場合は、リフレクションを使用して、シリアル化するオブジェクトのフィールドやプロパティを反復処理し、手動でそれらをMemoryStreamに書き込んで、重要な型をシリアル化する必要がある場合は再帰的にシリアル化を呼び出します。この方法はより複雑で、実装に時間がかかりますが、シリアル化されたストリームをはるかに制御できます。



1

「バイトにキャストする」という表現よりも「シリアライゼーション」という表現を使用したいと思います。オブジェクトのシリアル化とは、リモートボックスでオブジェクトを再構築するために使用できるバイト配列(またはXMLなど)にオブジェクトを変換することです。.NETでは、Serializable属性はオブジェクトをシリアル化できる型をマークします。


1

オブジェクトをバイト配列に変換する別の方法:

TypeConverter objConverter = TypeDescriptor.GetConverter(objMsg.GetType());
byte[] data = (byte[])objConverter.ConvertTo(objMsg, typeof(byte[]));

これを試してみましたが、.NET 4.6.1およびWindows 10では動作しませんでした
Contango

0

Newtonsoft.JsonバイナリJSON を使用し、[Serializable]属性ですべてをマークする必要がない、1つの追加の実装。唯一の欠点は、オブジェクトを匿名クラスでラップする必要があることです。そのため、バイナリシリアル化で取得されるバイト配列は、この配列と異なる場合があります。

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

BSONはクラスまたは配列で始まる必要があるため、匿名クラスが使用されます。byte []を逆シリアル化してオブジェクトに戻そうとはしていませんが、それが機能するかどうかはわかりませんが、byte []への変換速度をテストしましたが、私のニーズを完全に満たしています。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.