クラスのインスタンス(中身全て)をセーブ・ロードしたり、ネットを介したデータのやりとりをしたい……そんな時、インスタンスを転送用に 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 は利点がないので、今後は控えようと思います☺