如何反序列化结构等效的引用集合?

时间:2010-12-15 19:00:56

标签: c# .net-3.5 serialization binaryformatter

注意:随着我对该问题的了解越来越多,这个问题发生了一些变化,所以请完整阅读。我决定将其保留为原始形式,因为它更好地描述了问题是如何被发现并最终解决的。


回到我们项目历史上最黑暗的深度,当我们没有真正了解C#或CLR以及我们可能的时候,我们创建了一个类型,我们称之为MyType。我们将此类型创建为class,即引用类型。

然而,显而易见的是MyType应该是struct,这是一个值类型,所以我们做了一些更改以使其成为现实并且一直都很好,直到有一天,我们尝试反序列化一些数据包含MyType值的集合。嗯,不是真的,因为当它是引用类型时,该集合是引用的集合。现在当它反序列化时,集合反序列化很好,使用MyType的默认构造函数,然后当实际引用反序列化时,它们是孤立的,留下我们一组空值。

所以,我们认为,“让我们在加载时将类型映射到引用类型MyTypeRef,以便引用正确解析,然后转换回我们的实际类型,以便在执行时和重新序列化期间使用”。所以我们(使用我们自己的活页夹),但唉,它没有用,因为现在我们收到一个错误,告诉我们MyTypeRef[]即使我们有隐式转换也无法转换为MyType[]MyTypeRefMyType之间。

所以,我们被困住了。我们如何将序列序列化为引用类型MyType的集合以反序列化为值类型MyType的集合?

更新
一些调查(参见下面的评论和代码)表明,新MyType及其ISerializable用于序列化的不可改变的性质导致了真正的问题。我仍然不明白为什么会出现这种情况,但如果我使用private设置访问者而不是ISerializable,则新MyType将加载旧版{注意{{1}如果我使用它,则调用接口,但集合甚至只包含默认值。)

部分代码

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"); } } 并加载并完成所有工作。在加载代码上将ISerializable更改为class,并且可以看到相同的行为。就好像集合只会使用set访问器成功反序列化。

更新两个
所以,我发现了问题(请参阅下面的答案),但我怀疑是否有人会通过阅读我的问题来了解答案。我错过了一个我当时甚至都没有意识到的重要细节。我向那些试图帮助的人道歉。

struct期间,加载的包含MyType的集合会立即复制到另一个集合中。下面的答案解释了为什么这很重要。下面给出了一些额外的示例代码,它们应该提供一个完整的示例:

GetObjectData

2 个答案:

答案 0 :(得分: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”。那我错过了什么?

答案 1 :(得分:2)

我现在觉得有些愚蠢,但我发现了这个问题。我已使用其他示例代码更新了该问题。

以下是代码在GetObjectData方法中所做工作的基础知识:

  1. 加载集合
  2. 将该集合复制到另一个集合
  3. 以下是执行时发生的序列:

    1. 已加载集合,但元素不是
    2. 将null / default元素复制到另一个集合并丢弃已加载的集合变量
    3. 已加载集合的元素已反序列化
    4. 请注意,只有在第3点之后才会对整个集合进行反序列化,但我已经完成了我的副本,因此没有任何内容。

      修复是使用OnDeserialized attribute指定在反序列化整个对象后调用的方法。然后,在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;
      }