4

ご覧いただきありがとうございます。

現場で展開される製品の新しいバージョンに取り組んでいます。古いソフトウェアから既存のファイルを逆シリアル化する機能を維持する必要があります。

これは不自然な例です:

アクセスする必要があるシリアル化されたファイルを備えた既存の顧客ベースがあります。この質問の目的のために、彼らはキリンのリストを含む「Zoo」ファイルを持っています。

[Serializable]
public class Giraffe
    : ISerializable
{
    public int Age { get; private set; }

    public Giraffe(int age)
    {
        Age = age;
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Age", Age);
    }

    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    private Giraffe(SerializationInfo info, StreamingContext context)
    {
        Age = info.GetInt32("Age");
    }
}

現在、「Zoo」ソフトウェアの新しいバージョンを展開しており、キリン以外の動物をサポートする予定です。最初からこれを行うべきでしたが、時間の制約により、キリンで 1.0 をリリースする必要がありました-機能セットのみ。

public interface IAnimal
{
    int Age { get; }
}

[Serializable]
public class Animal
    : IAnimal,
    ISerializable
{
    public int Age { get; private set; }

    public Animal (int age)
    {
        Age = age;
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("Age", Age);
    }

    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    private Animal(SerializationInfo info, StreamingContext context)
    {
        Age = info.GetInt32("Age");
    }
}

カスタム serializationBinder を使用して、古いキリンを動物として逆シリアル化しています

public class LegacyZooSerializationBinder
    : SerializationBinder
{
    public override Type BindToType(string assemblyName, string typeName)
    {
        if (typeName.StartsWith("Test.Giraffe"))
            return typeof(Animal);
        else if (typeName.StartsWith("System.Collections.Generic.List`1[[Test.Giraffe"))
            return typeof(List<Animal>);
        else return null;
    }
}

問題は、Zoo クラスでリストではなく、ストレージとしてリストを使用したくないことです。これを行う理由は 2 つあります。1 つは将来の拡張性、もう 1 つは、単体テスト用に簡単にモックを作成できるようにするためです。

新しい IAnimal リストの逆シリアル化は問題ありません。古いスタイルのアイテムを逆シリアル化したいときに問題が発生します。Binder は正しい型を返し、正しい逆シリアル化コンストラクターが呼び出され、すべて問題ないように見えますが、List は実際には null 項目でいっぱいです。IDeserializationCallback.OnDeserialization コールバックが呼び出されると、リストは正しくなります。単純に IEnumerable.ConvertAll<>() を呼び出すことはできません。シリアル化フレームワークがすべてをクリーンアップするために戻ってきたときにまったく同じインスタンスを見つけようとしているように見え、ConvertAll が新しいリストを作成するからです。

現在は機能していますが、現時点ではそれほど保守可能ではないため、誰かがこれをクリーンアップするのを手伝ってくれることを願っています. そのために必要なことは次のとおりです。

[Serializable]
public class Zoo
    : ISerializable,
    IDeserializationCallback
{
    List<IAnimal> m_List = null;

    List<Giraffe> m_LegacyList = null; //Just so that we can save an old-style zoo

    //Temp copy of the list
    List<Animal> m_List_Deserialization_Temp_Copy = null;

    public Zoo(bool legacy)
    {
        m_List = new List<IAnimal>();

        if (legacy)
        {
            //Create an old style zoo, just for the example
            m_LegacyList = new List<Giraffe>();
            m_LegacyList.Add(new Giraffe(0));
            m_LegacyList.Add(new Giraffe(1));
        }
        else
        {
            m_List.Add(new Animal(0));
            m_List.Add(new Animal(1));
        }
    }

    public List<IAnimal> List
    {
        get { return m_List; }
    }

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
    {
        if(m_LegacyList != null) //Save as an old style list if we have old data
            info.AddValue("list", m_LegacyList);
        else
            info.AddValue("list", m_List);
    }

    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    private Zoo(SerializationInfo info, StreamingContext context)
    {
        try
        {
            //New style
            m_List = (List<IAnimal>)info.GetValue("list", typeof(List<IAnimal>));
        }
        catch (InvalidCastException)
        {
            //Old style
            //Put it in a temp list, until the OnDeserialization callback is called, this will be a list, full of null items!
            m_List_Deserialization_Temp_Copy = (List<Animal>)info.GetValue("list", typeof(List<Animal>));
        }
    }

    void IDeserializationCallback.OnDeserialization(object sender)
    {
        if (m_List_Deserialization_Temp_Copy != null)
        {
            m_List = new List<IAnimal>();

            //This works because IEnumerable<Animal> is covariant to IEnumerable<IAnimal>
            m_List.AddRange(m_List_Deserialization_Temp_Copy);
        }
    }
}

以下は、両方のケースのシリアル化と逆シリアル化を示す基本的なテスト コンソール アプリです。

    static void Main(string[] args)
    {
        {
            var item = new Zoo(false);

            var formatter = new BinaryFormatter();
            var stream = new MemoryStream();

            formatter.Serialize(stream, item);

            stream.Position = 0;

            formatter.Binder = new LegacyZooSerializationBinder();

            var deserialized = (Zoo)formatter.Deserialize(stream);

            Debug.Assert(deserialized.List.Count == 2);
            Debug.Assert(deserialized.List[0] != null);
            Debug.Assert(deserialized.List[0].Age == 0);

            Debug.Assert(deserialized.List[1] != null);
            Debug.Assert(deserialized.List[1].Age == 1);

            Console.WriteLine("New-style Zoo serialization OK.");
        }

        {
            var item = new Zoo(true);

            var formatter = new BinaryFormatter();
            var stream = new MemoryStream();

            formatter.Serialize(stream, item);

            stream.Position = 0;

            formatter.Binder = new LegacyZooSerializationBinder();

            var deserialized = (Zoo)formatter.Deserialize(stream);

            Debug.Assert(deserialized.List.Count == 2);
            Debug.Assert(deserialized.List[0] != null);
            Debug.Assert(deserialized.List[0].Age == 0);

            Debug.Assert(deserialized.List[1] != null);
            Debug.Assert(deserialized.List[1].Age == 1);

            Console.WriteLine("Old-style Zoo serialization OK.");
        }

        Console.ReadKey();
    }

どんな提案でも大歓迎です。この種の良いリソースを見つけるのに苦労しています。ありがとう!

4

1 に答える 1

3

古いファイルから新しい形式への一度の変換を行うことを検討してください。できればインストール時に、必ずバックアップ後に行ってください。そうすれば、この奇妙な 1 回限りのシリアライゼーションを永遠にサポートする必要がなくなり、コードベースが大幅に簡素化されます。

于 2012-03-13T19:43:02.897 に答える