読者です 読者をやめる 読者になる 読者になる

謎言語使いの徒然

適当に気になった技術や言語を流すブログ。

BinaryFormatter 経由で、オブジェクトをシリアライズとデシリアライズ

勉強 .NET Tips

シリアライズ対象。

[Serializable]
public class Game : IDeserializationCallback
{
    public HardwereType[] Hard { get; set; }
    public String Title { get; set; }
    public String Genre { get; set; }
    public uint Price { get; set; }

    public override string ToString()
    {
        return string.Format(
            "Title:{0}, Genre:{1}, Hardwere:{3} Price:{2}",
            Title, Genre, Price, HardList(Hard));
    }

    private static string HardList(HardwereType[] list)
    {
        StringBuilder builder = new StringBuilder();
        foreach (HardwereType data in list)
        {
            builder.Append(data);
            builder.Append(' ');
        }

        return builder.ToString();
    }
}

[Serializable]
public enum HardwereType
{
    PS3, XBox360, PSP, Wii, DS
}

これをサクっとシリアライズとデシリアライズ

Game vanquish = new Game() {
    Genre = "FPS",
    Hard = new HardwereType[2] { HardwereType.PS3, HardwereType.XBox360},
    Price = 7980,
    Title = "VANQUISH"
};
            
BinaryFormatter formatter = new BinaryFormatter();
            
using (FileStream writer = new FileStream("sample.dat", FileMode.Create))
{
    formatter.Serialize(writer, vanquish);
}

using (FileStream reader = new FileStream("sample.dat", FileMode.Open))
{
    Game loaded = (Game)formatter.Deserialize(reader);
    Console.WriteLine(loaded);
}

ここで、互換性を保ったままクラスを拡張してみる。


(Ageの実装はObject指向的に間違ってるけど、サンプルの為)

[Serializable]
public class Game : IDeserializationCallback
{
    /* 略 */

    [OptionalField]
    public String CERO = "A";

    [NonSerialized]
    public int Age;

    public override string ToString()
    {
        return string.Format(
            "Title:{0}, CERO:{4}({5}), Genre:{1}, Hardwere:{3} Price:{2}",
            Title, Genre, Price, HardList(Hard), CERO, Age);
    }

    private static string HardList(HardwereType[] list)
    {
        StringBuilder builder = new StringBuilder();
        foreach (HardwereType data in list)
        {
            builder.Append(data);
            builder.Append(' ');
        }

        return builder.ToString();
    }

    void IDeserializationCallback.OnDeserialization(object sender)
    {
        switch (this.CERO)
        {
            case "A":
                this.Age = 0;
                break;
            case "B":
                this.Age = 12;
                break;
            case "C":
                this.Age = 15;
                break;
            case "D":
                this.Age = 17;
                break;
            case "Z":
                this.Age = 18;
                break;
            default:
                this.Age = 0;
                break;
        }
    }
}

これで、プログラムを下記だけにして走らせてみる。

BinaryFormatter formatter = new BinaryFormatter();

using (FileStream reader = new FileStream("sample.dat", FileMode.Open))
{
    Game loaded = (Game)formatter.Deserialize(reader);
    Console.WriteLine(loaded);
}

重要なのは以下の点か。

  • Serialized なフィールドは削除しない。
  • NonSerialized 属性は、既存項目に設定しない。
  • フィールドの型変更や、名称変更をしない。
  • optional field atttibute を新規フィールドに突っ込み
  • カスタムなserialization を定義してバージョン差を埋める
  • NonSerialized 属性を既存項目から削除する際は、OptionalField 属性を付ける。
  • OptionalFirld には、意味のあるデフォルト値を設定する。