C#でオブジェクトをバイト配列に変換する方法


99

バイナリファイルに書き込む必要があるオブジェクトのコレクションがあります。

ファイルのバイトをコンパクトにする必要があるため、を使用できませんBinaryFormatterBinaryFormatter逆シリアル化の必要性のためにあらゆる種類の情報を投入します。

私がしようとすると

byte[] myBytes = (byte[]) myObject 

ランタイム例外が発生します。

これは高速である必要があるので、バイト配列をコピーするのではなく、キャストbyte[] myBytes = (byte[]) myObjectが機能してほしい!

OKただ明確にすることが、私がすることはできません任意の出力ファイルのメタデータを。オブジェクトのバイトのみ。パックされたオブジェクト間。受け取った回答に基づくと、低レベルのBuffer.BlockCopyコードを書いているようです。おそらく安全でないコードを使用しています。

回答:


173

オブジェクトをバイト配列に変換するには:

// Convert an object to a byte array
public static byte[] ObjectToByteArray(Object obj)
{
    BinaryFormatter bf = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        bf.Serialize(ms, obj);
        return ms.ToArray();
    }
}

この関数をコードにコピーして、バイト配列に変換する必要のあるオブジェクトを送信するだけです。バイト配列をオブジェクトに再度変換する必要がある場合は、以下の関数を使用できます。

// Convert a byte array to an Object
public static Object ByteArrayToObject(byte[] arrBytes)
{
    using (var memStream = new MemoryStream())
    {
        var binForm = new BinaryFormatter();
        memStream.Write(arrBytes, 0, arrBytes.Length);
        memStream.Seek(0, SeekOrigin.Begin);
        var obj = binForm.Deserialize(memStream);
        return obj;
    }
}

これらの関数をカスタムクラスで使用できます。[Serializable]シリアル化を有効にするには、クラスに属性を追加するだけです


9
私はこれを試しました、そしてそれはあらゆる種類のメタデータを追加しました。OPは彼がメタデータを望んでいないと述べました。
user316117 2013年

4
言うまでもなく、誰もが、シリアライズしようとしているのは、あなたが書いたもの、またはすでにシリアライズされるように事前設定されているものであると想定しているようです。
Hexum064

3
MemoryStream2番目のコード例では、バイト配列を直接コンストラクタに渡すことができます。これにより、Write(...)およびの使用が排除されますSeek(...)
unknown6656

41

シリアル化されたデータを本当にコンパクトにしたい場合は、シリアル化メソッドを自分で作成できます。そうすることで、オーバーヘッドを最小限に抑えることができます。

例:

public class MyClass {

   public int Id { get; set; }
   public string Name { get; set; }

   public byte[] Serialize() {
      using (MemoryStream m = new MemoryStream()) {
         using (BinaryWriter writer = new BinaryWriter(m)) {
            writer.Write(Id);
            writer.Write(Name);
         }
         return m.ToArray();
      }
   }

   public static MyClass Desserialize(byte[] data) {
      MyClass result = new MyClass();
      using (MemoryStream m = new MemoryStream(data)) {
         using (BinaryReader reader = new BinaryReader(m)) {
            result.Id = reader.ReadInt32();
            result.Name = reader.ReadString();
         }
      }
      return result;
   }

}

いくつかのintといくつかの文字列を書くにはどうすればよいですか?
スミス

1
@スミス:ええ、あなたはそれを行うことができます、ただお互いに続けて書いてください。BinaryWriter形式でそれらを書き込みますBinaryReader限り、あなたが書いたと同じ順序でそれらを読んで、読むことができます。
グッファ

1
違いは何であるBinaryWriter/Readerと使用してBinaryFormatter
スミス

3
@Smith:BinaryWriter/Reader自分でシリアライゼーション/デシリアライゼーションを自分で行うと、絶対に必要なデータのみをできるだけコンパクトに書き込み/読み取りできます。BinaryFormatter用途書き込みにどのようなデータ読み取り/アウト見つけるための反射、およびすべての可能な場合のために働くのフォーマットを使用しています。また、ストリームのフォーマットに関するメタ情報も含まれているため、オーバーヘッドがさらに増えます。
Guffa

1
@スミス:enumをキャストしてint(またはenumのストレージとして他のタイプを指定した場合)、それを書き込むことができます。それを読んだら、列挙型にキャストできます。
Guffa

31

井戸からのキャストmyObjectには、byte[]あなたがた場合、または明示的な変換を持っていない限り決して仕事に行くされていないmyObject ですbyte[]何らかのシリアル化フレームワークが必要です。私にとって近くて大切なプロトコルバッファを含め、たくさんあります。空間的にも時間的にもかなり「無駄のない」意味です。

ただし、ほとんどすべてのシリアル化フレームワークには、シリアル化できるものに大きな制限があることがわかります。ただし、クロスプラットフォームであるため、プロトコルバッファーはいくつかのものよりも多くなります。

より多くの要件を与えることができれば、私たちはあなたをさらに助けることができます-しかし、それはキャストするほど簡単ではありません...

編集:これに応答するだけです:

オブジェクトのバイトを含むバイナリファイルが必要です。バイトのみで、メタデータは一切ありません。パックされたオブジェクト間。だから私はカスタムシリアル化を実装します。

オブジェクトのバイトは参照であることが多いので注意してください。そのため、それらのバイトをどう処理するかを検討する必要があります。

独自のカスタムシリアル化フレームワークの設計と実装が想像以上に難しいことに気付くと思います。

私が個人的に推奨するのは、これをいくつかの特定のタイプに対してのみ行う必要がある場合は、一般的なシリアル化フレームワークを考え出そうとしないことです。必要なすべてのタイプにインスタンスメソッドと静的メソッドを実装するだけです。

public void WriteTo(Stream stream)
public static WhateverType ReadFrom(Stream stream)

心に留めておくべきことの1つは、継承が関与している場合、すべてがよりトリッキーになります。継承がなければ、どのタイプから始めるかがわかっていれば、タイプ情報を含める必要はありません。もちろん、バージョン管理の問題もあります。タイプの異なるバージョンとの後方互換性と前方互換性について心配する必要がありますか?


これを「protobuf-csharp-port」(Googleコード)または「dotnet-protobufs」(Git)と呼ぶ方が正しいですか?
Marc Gravell

1
オブジェクトのバイトを含むバイナリファイルが必要です。バイトのみで、メタデータは一切ありません。パックされたオブジェクト間。だから私はカスタムシリアル化を実装します。
チャックローガン2009

6
リスクゼロメタデータは、あなたがしているということです非常にそれが手遅れになる前に、柔軟性を可能にする非常にいくつかの方法を持っているとして、バージョン不耐症。プロトコルバッファはかなりデータ密度が高いです。あなたは本当にねじの余分な回転が必要ですか?
Marc Gravell

@Marc:もちろん、整数の場合、PBは生のバイトよりも密度が高くなる可能性があります...
Jon Skeet

16

Crystalonicsの回答を受け取り、拡張メソッドに変換しました。私は他の誰かがそれらが役立つことを願っています:

public static byte[] SerializeToByteArray(this object obj)
{
    if (obj == null)
    {
        return null;
    }
    var bf = new BinaryFormatter();
    using (var ms = new MemoryStream())
    {
        bf.Serialize(ms, obj);
        return ms.ToArray();
    }
}

public static T Deserialize<T>(this byte[] byteArray) where T : class
{
    if (byteArray == null)
    {
        return null;
    }
    using (var memStream = new MemoryStream())
    {
        var binForm = new BinaryFormatter();
        memStream.Write(byteArray, 0, byteArray.Length);
        memStream.Seek(0, SeekOrigin.Begin);
        var obj = (T)binForm.Deserialize(memStream);
        return obj;
    }
}

1
これは本当に便利で簡単です!! ありがとうございました。
MrHIDEn

13

あなたは本当にシリアライゼーションについて話している、それは多くの形をとることができる。小さいバイナリが必要なため、プロトコルバッファは実行可能なオプションである可能性があります-バージョンの許容範囲と移植性も提供します。とは異なりBinaryFormatter、プロトコルバッファーのワイヤ形式には、すべてのタイプのメタデータが含まれていません。データを識別するための非常に簡潔なマーカー。

.NETにはいくつかの実装があります。特に

私は謙虚に主張したいこといるProtobufネット(私が書いた)典型的なC#クラス(「通常の」プロトコルバッファがする傾向があると、より.NET-慣用的使用を可能に需要コード生成); 例えば:

[ProtoContract]
public class Person {
   [ProtoMember(1)]
   public int Id {get;set;}
   [ProtoMember(2)]
   public string Name {get;set;}
}
....
Person person = new Person { Id = 123, Name = "abc" };
Serializer.Serialize(destStream, person);
...
Person anotherPerson = Serializer.Deserialize<Person>(sourceStream);

1
「簡潔なマーカー」でさえメタデータのままです。OPが何を求めているかについての私の理解は、オブジェクトのデータに他なりませんでした。したがって、たとえば、オブジェクトが2つの32ビット整数を持つ構造体である場合、結果は8バイトのバイト配列であると期待します。
user316117 2013年

@ user316117これは、バージョン管理にとって非常に困難です。それぞれのアプローチには長所と短所があります。
Marc Gravell


Proto *属性の使用を回避する方法はありますか?使用したいエンティティがサードパーティのライブラリにあります。
Alex 75

5

これは私のために働きました:

byte[] bfoo = (byte[])foo;

foo バイト配列であることが100%確実であるオブジェクトです。


2

オブジェクト全体をバイトストリームに「変換」する手法であるSerializationを見てください。ネットワークに送信するか、ファイルに書き込んでから、後でオブジェクトに復元することができます。


チャックローガンはそれを明示的に拒否したと思います(フォーマッター==シリアル化)。
ヘンクホルターマン

@ヘンク-それは理由が何であるかに依存します。彼は追加の情報について言及しました。これはタイプメタデータとフィールド情報であると私は考えています。そのオーバーヘッドなしでシリアライゼーションを使用できます。だけではありませんBinaryFormatter
Marc Gravell

2

オブジェクトをバイトに変換する別の方法を見つけました[]、これが私の解決策です:

IEnumerable en = (IEnumerable) myObject;
byte[] myBytes = en.OfType<byte>().ToArray();

よろしく


1

オブジェクトのメモリに直接アクセスするには(「コアダンプ」を行うため)、安全でないコードに向かう必要があります。

BinaryWriterよりもコンパクトなものが必要な場合や、生のメモリダンプで十分な場合は、オブジェクトから重要な情報を抽出して最適な方法でパックするカスタムシリアル化コードを記述する必要があります。

編集する PSの BinaryWriterアプローチをDeflateStreamにラップしてデータを圧縮するのは非常に簡単です。これにより、通常、データのサイズが約半分になります。


1
安全でないコードでは十分ではありません。C#とCLRでは、安全でないコードでも、マネージオブジェクトへの生のポインターを取得したり、2つのオブジェクト参照をユニオンに入れたりすることはできません。
Pavel Minaev

0

あなたがやろうとしていることは不可能だと思います。作成する

ジャンクBinaryFormatterは、プログラムが停止した後にファイルからオブジェクトを回復するために必要です。
ただし、オブジェクトデータを取得することは可能です。正確なサイズを知る必要があるだけです(思ったより難しい)。

public static unsafe byte[] Binarize(object obj, int size)
{
    var r = new byte[size];
    var rf = __makeref(obj);
    var a = **(IntPtr**)(&rf);
    Marshal.Copy(a, r, 0, size);
    return res;
}

これは次の方法で回復できます:

public unsafe static dynamic ToObject(byte[] bytes)
{
    var rf = __makeref(bytes);
    **(int**)(&rf) += 8;
    return GCHandle.Alloc(bytes).Target;
}

上記のメソッドがシリアル化に機能しない理由は、返されるデータの最初の4バイトがに対応するためRuntimeTypeHandleです。RuntimeTypeHandleオブジェクトのレイアウト/タイプが記載されているが、その値は、プログラムが実行されるたびに変更されます。

編集:それは愚かですそれをしないでください-> あなたがすでにデシリアライズされるオブジェクトのタイプを確実に知っているなら、あなたBitConvertes.GetBytes((int)typeof(yourtype).TypeHandle.Value)はデシリアライズ時にそれらのバイトを切り替えることができます。

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