2

注:この質問は、問題について詳しく知るにつれて少し変わったので、全体を読んでください。問題がどのように発見され、最終的に解決されたかをよりよく説明しているため、元の形式のままにすることにしました。


私たちがC#やCLRを本当に理解していなかったとき、私たちのプロジェクトの歴史の最も暗い深さまでさかのぼって、私たちはタイプを作成しました。それを呼びましょうMyType。この型をclass参照型として作成しました。

MyTypeしかし、値型である必要があることが明らかになっstructたので、それを行うためにいくつかの変更を加えました。ある日まで、MyType値のコレクションを含むいくつかのデータを逆シリアル化しようとしました。そうではありませんが、それが参照型であった場合、そのコレクションは参照のコレクションでした。これで、逆シリアル化すると、コレクションはデフォルトのコンストラクターを使用して正常に逆シリアル化され、MyType後で実際の参照が逆シリアル化されると、それらは孤立し、空の値のコレクションが残ります。

そこで、「ロード時に、参照が適切に解決されるように型を参照型にマップしてから、MyTypeRef実行時と再シリアル化で使用するために実際の型に変換し直してみましょう」と考えました。したがって、(独自のバインダーを使用して)実行しましたが、残念ながら、との間の暗黙の変換があってもMyTypeRef[]変換できないことを示すエラーが発生するため、機能しませんでした。MyType[]MyTypeRefMyType

だから、私たちは立ち往生しています。MyType参照型のコレクションとしてシリアル化されたコレクションを取得して、値型のコレクションとして逆シリアル化するにはどうすればよいMyTypeですか?

更新
いくつかの調査(以下のコメントとコードを参照)は、実際の問題を引き起こしたのは、新しいものの不変の性質であり、シリアル化にMyType使用されていることを示しています。ISerializableなぜそうなるのかはまだわかりませんが、のprivate代わりにsetアクセサーを使用するISerializableと、新しいMyTypeものが古いものをロードします(ISerializable使用するとインターフェイスが呼び出されますが、コレクションにはデフォルト値しか含まれていません)。

いくつかのコード

// Use a List<T> in a class that also implements ISerializable and
// save an instance of that class with a BinaryFormatter (code omitted)

// Save with this one.
[Serializable]
public class MyType
{
    private string test;
    public string Test
    {
        get { return this.test; }
        set { this.test = value; }
    }

    public MyType()
    {
    }
}

// Load with this one.
[Serializable]
public class MyType : ISerializable
{
    private string test;
    public string Test
    {
        get { return this.test; }
        set { this.test = value; }
    }

    public MyType()
    {
    }

    public MyType(SerializationInfo info, StreamingContext context)
    {
        info.AddValue("test", this.test);
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        this.test = info.GetString("test");
    }
}

ロードするとき、要素はすべてnullであることに注意してください。2番目の定義から削除ISerializableし、ロードするとすべてが機能します。ロードコードをオンに変更classすると、同じ動作が表示されます。structこれは、コレクションが設定されたアクセサーを使用してのみ正常に逆シリアル化されるかのようです。

更新2
それで、私は問題を見つけました(以下の私の答えを参照してください)が、私の質問を読んで誰かが答えを知っているとは思えません。当時重要だとさえ気づかなかった重要な詳細を見逃しました。助けようとした人たちに心からお詫び申し上げます。

ロードMyTypeされたコレクションは、の間にすぐに別のコレクションにコピーされGetObjectDataます。以下の答えは、これが重要であることが判明した理由を説明しています。完全な例を提供する、上記のサンプルコードに追加するサンプルコードを次に示します。

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
    this.myTypeCollection = new List<MyType>();
    var loadedCollection = (List<MyType>)info.GetValue(
        "myTypeCollection",
        typeof(List<MyType>));
    this.myTypeCollection.AddRange(loadedCollection);
}
4

2 に答える 2

2

理解するかどうかはわかりませんが、保存時にこれを行うと、次のようになります。

[Serializable]
public class MyTypeColl
{
    public MyTypeColl()
    {
    }

    public List<MyType> Coll { get; set; }
}

[Serializable]
public class MyType
{
    private string test;
    public string Test
    {
        get { return this.test; }
        set { this.test = value; }
    }

    public MyType()
    {
    }
}

// save code

    MyTypeColl coll = new MyTypeColl();
    coll.Coll = new List<MyType>();
    coll.Coll.Add(new MyType{Test = "MyTest"});

    BinaryFormatter bf = new BinaryFormatter();
    using (FileStream stream = new FileStream("test.bin", FileMode.OpenOrCreate))
    {
        bf.Serialize(stream, coll);
    }

そしてこれはロード時に:

[Serializable]
public struct MyType
{
    private string test;
    public string Test
    {
        get { return this.test; }
        set { this.test = value; }
    }
}

// load code

    BinaryFormatter bf = new BinaryFormatter();
    using (FileStream stream = new FileStream("test.bin", FileMode.Open))
    {
        MyTypeColl coll = (MyTypeColl)bf.Deserialize(stream);
        Console.WriteLine(coll.Coll[0].Test);
    }

「MyTest」が正常に表示されます。だから私は何が欠けていますか?

于 2010-12-15T20:53:30.570 に答える
2

今は少し馬鹿げていますが、問題を発見しました。追加のサンプルコードで質問を更新しました。

GetObjectDataこのメソッドでコードが実行していたことの基本は次のとおりです。

  1. コレクションをロードする
  2. コレクションを別のコレクションにコピーします

実行時に発生したシーケンスは次のとおりです。

  1. コレクションは読み込まれましたが、要素は読み込まれていません
  2. null / default要素を別のコレクションにコピーし、ロードされたコレクション変数を破棄しました
  3. ロードされたコレクションの要素は逆シリアル化されます

コレクション全体が逆シリアル化されるのはポイント3の後であることに注意してください。ただし、私はすでにコピーを行っているため、何もありません。

修正は、OnDeserialized属性を使用して、オブジェクト全体が逆シリアル化されたときに呼び出すメソッドを指定することでした。次に、ロードされたコレクションへの参照を保存しますが、完全に逆シリアル化されたらGetObjectData、その内容をメソッドにコピーします。OnDeserialized

補足
実際、逆シリアル化コードをすべて保持するGetObjectDataために、コピーを実行するためにデリゲートを保存し、後でそのデリゲートを呼び出しますGetObjectData。そうすれば、逆シリアル化が完了するまでに何が起こるかが明確になります。

コード

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
    var loadedCollection = (List<MyType>)info.GetValue(
        "myTypeCollection",
        typeof(List<MyType>));

    this.myTypeCollectionLoader = () =>
    {
        this.myTypeCollection.AddRange(loadedCollection);
    };
}

[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
    this.myTypeCollectionLoader();
    this.myTypeCollectionLoader = null;
}
于 2010-12-20T18:54:21.203 に答える