クラスのインスタンス(中身全て)をセーブ・ロードしたり、ネットを介したデータのやりとりをしたい……そんな時、インスタンスを転送用に byte 配列にする方法をいくつか紹介します。
なお、素直に Json でよくない? というのもごもっとも。今回はインスタンス値の CRC を作ったり、個人的な要件があって byte[] の変換を調査しました。
byte[] に変換するサンプルインスタンスは次の通りです。
public class Sample
{
public const int SLOT_AUTOSAVE = 0;
public bool Used;
public string DisplayNo;
public int SlotNo;
public DateTime Time;
public string Description;
public int Crc;
}
Sample sample = new Sample()
{
Used = true,
DisplayNo = "99",
SlotNo = Sample.SLOT_AUTOSAVE,
Time = DateTime.Now,
Description = "テストデータです",
};
1.BinaryFormatter
名前からして思い浮かびやすいのは、まずこれでしょうか。
クラスのインスタンスから byte[] の変換、byte[] からインスタンスに変換、それぞれのコードを示します。
注意点として、変換するクラスは [Serializable] という属性をつけなければならないことです。
そのため、クラスの中にクラス変数を宣言している(そちらはライブラリのため、気軽に自分でコード追加できない)といった場合は難しいかもしれません。
byte[] のサイズは 217 バイトでした。(マシン環境によって異なるかもしれません)
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
[Serializable]
public class Sample
{
public const int SLOT_AUTOSAVE = 0;
public bool Used;
public string DisplayNo;
public int SlotNo;
public DateTime Time;
public string Description;
public int Crc;
}
Sample sample = new Sample()
{
Used = true,
DisplayNo = "99",
SlotNo = Sample.SLOT_AUTOSAVE,
Time = DateTime.Now,
Description = "テストデータです",
};
void binaryFormatter()
{
// instance → byte[]
byte[] bytes;
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter writer = new BinaryFormatter();
writer.Serialize(ms, sample);
bytes = ms.ToArray();
}
// byte[] → instance
Sample output;
using (MemoryStream ms = new MemoryStream(bytes))
{
BinaryFormatter reader = new BinaryFormatter();
output = (Sample)reader.Deserialize(ms);
}
}
2.Marshal
4 行目、StructLayout の属性が必要なことに注意してください。
コードも少し多めです。この中で、Free 系は絶対忘れないようにしてください(メモリーリークします)。
また、今回のサンプルクラスは残念ながら以下のエラーが出てしまいます。
MarshalDirectiveException: Type System.DateTime which is passed to unmanaged code must have a StructLayout attribute.
System.DateTime はマーシャリングをサポートしていないようです。
これを解決する方法もなくはないですが、楽をして相互変換をしたいのに手間が増えるのは御免ですよね。
ちなみに、DateTime がなければ実行は可能です。
byte[] のサイズは 28 バイトと結構小さめにできますが、そもそも DateTime は含められませんし、普段使いするには敷居が高い印象です。
using System;
using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)]
public class Sample
{
public const int SLOT_AUTOSAVE = 0;
public bool Used;
public string DisplayNo;
public int SlotNo;
public DateTime Time; //← これがあるとエラー
public string Description;
public int Crc;
}
Sample sample = new Sample()
{
Used = true,
DisplayNo = "99",
SlotNo = Sample.SLOT_AUTOSAVE,
Time = DateTime.Now, //← これがあるとエラー
Description = "テストデータです",
};
void marshal()
{
// instance → byte[]
var size = Marshal.SizeOf(sample);
var bytes = new byte[size];
var ptr = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(sample, ptr, false);
Marshal.Copy(ptr, bytes, 0, size);
Marshal.FreeHGlobal(ptr);
// byte[] → instance
var handle = GCHandle.Alloc(bytes, GCHandleType.Pinned);
Sample output = (Sample)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Sample));
handle.Free();
}
3.Json を経由
コードはシンプル。byte[] のサイズも 133 バイトと、BinaryFormatter よりは小さめです。
UTF8 を GetEncoding("Shift-JIS") にするともっと小さくなるものの、日本語・英語以外の言語対応を考えるなら UTF8 がよさそうですね。
また、NewtonSoft.Json を使う限りにおいて、クラスに属性をつける必要もありません。
扱いやすさでは3つの中で間違いなくトップ。
ただ、なんとなくイメージ的に遅いんじゃないか……? と懸念を感じたので、3種それぞれの実行速度を比べてみることにします。
using System.Text;
using Newtonsoft.Json;
public class Sample
{
public const int SLOT_AUTOSAVE = 0;
public bool Used;
public string DisplayNo;
public int SlotNo;
public DateTime Time;
public string Description;
public int Crc;
}
Sample sample = new Sample()
{
Used = true,
DisplayNo = "99",
SlotNo = Sample.SLOT_AUTOSAVE,
Time = DateTime.Now,
Description = "テストデータです",
};
void toJson()
{
// instance → byte[]
byte[] bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(sample));
// byte[] → instance
Sample output = JsonConvert.DeserializeObject<Sample>(Encoding.UTF8.GetString(bytes));
}
3種類の速度を比較する
それぞれを1万回繰り返した速度比較を示します。
なお、Marshal は DateTime が使えないため、テストでは DateTime を除いたクラスで行いました。

速度だけでみると圧倒的に Marshal が速いようです。
また、一番遅いと予想していた ToJson が2番手だったのが意外でした。
BinaryFormatter はシリアライズより、デシリアライズが重そうです。シリアライズだけなら 278ms でした(ToJson は 220ms)。
以上から、普段使いには ToJson、速度を求めるのであれば Marshal(ただしマーシャリング出来ないクラスもある) がよさそうです。
BinaryFormatter は利点がないので、今後は控えようと思います☺



