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

チリペヂィア

リンクフリー。サンプルコードなどは関連記事内でライセンスについて明示されない限り商用利用なども自由に行って構いませんが、自己責任でお願いします。またこれら日記内容の著作権自体は放棄していません。引用部分については引用元の権利に従ってください。

ISerializableなクラスをシリアライズして、別のISerializableなクラスにデシリアライズ(型変換)

SerializationBinderクラスを継承してIFormatter.Binderをカスタマイズすると、デシリアライズ時の変換先の型を設定できるようです(*1)。つまりは一種の型変換です。でわ早速、「3次元ベクトルクラスのz要素を切り落として2次元ベクトルクラスに変換」を、BinaryFormatterによるシリアライズ経由で試してみます(*2)。

まずISerializableな2Dベクトル

[System.Serializable] public class MyVector2 : System.Runtime.Serialization.ISerializable
{
    public float x { get { return m_x; } set { m_x = value; } }
    public float y { get { return m_y; } set { m_y = value; } }
    public MyVector2() { m_x = .0f; m_y = .0f; }
    public MyVector2(float _x, float _y) { m_x = _x; m_y = _y; }
    // デシリアライズコンストラクタ
    protected MyVector2(System.Runtime.Serialization.SerializationInfo info,
                     System.Runtime.Serialization.StreamingContext stream)
    {
        try { m_x = info.GetSingle("x"); }
        catch (System.Exception) { m_x = .0f; }

        try { m_y = info.GetSingle("y"); }
        catch (System.Exception) { m_y = .0f; }
    }
    // シリアライズメソッド
    public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
    {
        info.AddValue("x", m_x);
        info.AddValue("y", m_y);
    }
    public override System.String ToString() { return System.String.Format("({0:0.000}, {1:0.000})", m_x, m_y); }
    private float m_x, m_y;
};

で、z要素を増やしただけの3Dバージョン。

[System.Serializable] public class MyVector3 : System.Runtime.Serialization.ISerializable
{
    public float x { get { return m_x; } set { m_x = value; } }
    public float y { get { return m_y; } set { m_y = value; } }
    public float z { get { return m_z; } set { m_z = value; } }
    public MyVector3() { m_x = .0f; m_y = .0f; m_z = .0f; }
    public MyVector3(float _x, float _y, float _z) { m_x = _x; m_y = _y; m_z = _z; }
    // デシリアライズコンストラクタ
    protected MyVector3(    System.Runtime.Serialization.SerializationInfo info,
                            System.Runtime.Serialization.StreamingContext stream)
    {
        try { m_x = info.GetSingle("x"); }
        catch (System.Exception) { m_x = .0f; }

        try { m_y = info.GetSingle("y"); }
        catch (System.Exception) { m_y = .0f; }

        try { m_z = info.GetSingle("z"); }
        catch (System.Exception) { m_z = .0f; }
    }
    // シリアライズメソッド
    public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
    {
        info.AddValue("x", m_x);
        info.AddValue("y", m_y);
        info.AddValue("z", m_z);
    }
    public override System.String ToString() { return System.String.Format("({0:0.000}, {1:0.000}, {2:0.000})", m_x, m_y, m_z); }
    private float m_x, m_y, m_z;
};

無理やりキャストするためのSerializationBinderを定義。

public class TypeCastSerializeBinder : System.Runtime.Serialization.SerializationBinder
{
    // コンストラクタにキャスト先の型を指定すると、是が非でもその型へのデシリアライズをトライするようになる。
    public TypeCastSerializeBinder(System.Type castToType) { m_t = castToType; }
    public override System.Type BindToType(System.String assemblyName, System.String typeName) { return m_t; }
    private System.Type m_t;
};

3Dクラスをシリアライズして、そのStreamデータから2Dクラスにデシリアライズ

class MainClass
{
    static void Main(string[] args)
    {
        MyVector3 vecA = new MyVector3(1.0f, 1.0f, 1.0f);
        MyVector2 vecB = new MyVector2(-1.0f, -1.0f);
        System.Console.WriteLine("vecA:" + vecA.ToString());
        System.Console.WriteLine("vecB:" + vecB.ToString());
        using (System.IO.Stream stream = new System.IO.MemoryStream())
        {
            // serialize
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter serialize_formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
            serialize_formatter.Serialize(stream, vecA);

            // stream seek
            stream.Position = 0;

            // deserialize
            System.Runtime.Serialization.Formatters.Binary.BinaryFormatter deserialize_formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
                // ↓ここでBinderを設定!
            deserialize_formatter.Binder = new TypeCastSerializeBinder( typeof(MyVector2) );
            vecB = deserialize_formatter.Deserialize(stream) as MyVector2;
        }
        System.Console.WriteLine("vecB(deserialize from vecA):" + vecB.ToString());
        System.Console.ReadKey();
    }
};

出力結果

vecA:(1.000, 1.000, 1.000)
vecB:(-1.000, -1.000)
vecB(deserialize from vecA):(1.000, 1.000)

この様式だと、デシリアライズ時のコンストラクトを好きに処理できるので、変換失敗時の処理を好きにしやすいのが便利といえば便利かもしれません。それに、SerializationBinderの継承のところ、もうちょっとダイナミックなアセンブリからの型検索にも使える雰囲気です。

*1:そもそも、SerializationInfoに対する操作がSet/GetValueだけで完結する様式と言い、SerializationInfoの保有する型情報が曖昧でも許容する仕様と良い、SerializationInfo自体はもともと、変換する型にはそう強く結び付けられた情報ではなく、メンバをあらわす要素名とデータがセットにされただけの、わりと柔軟なフォーマットに見えます

*2:「変換コンストラクタで良いじゃんよ」いやまったく。単に変換前後のメンバー欠損とか包含関係が分かりやすかったから…