2

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


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

然而,很明显MyType应该是struct一个值类型,所以我们进行了一些更改以使其如此,一切都很好,直到有一天,我们尝试反序列化一些包含值集合的数据MyType。嗯,不是真的,因为当它是一个引用类型时,那个集合就是一个引用的集合。现在,当它反序列化时,集合可以很好地反序列化,使用 的默认构造函数MyType,然后当实际引用反序列化时,它们是孤立的,给我们留下一个空值集合。

因此,我们想,“让我们在加载时将类型映射到引用类型,MyTypeRef以便引用正确解析,然后转换回我们的真实类型以在执行时和重新序列化期间使用”。所以我们做了(使用我们自己的活页夹),但是可惜,它不起作用,因为现在我们得到一个错误,告诉我们即使我们在and之间进行了隐式转换,MyTypeRef[]也无法转换为。MyType[]MyTypeRefMyType

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

更新
一些调查(见下面的评论和代码)表明,新的不可变性质MyType及其用于ISerializable序列化的特性导致了真正的问题。我仍然不明白为什么会这样,但是如果我使用privateset 访问器而不是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");
    }
}

请注意,加载时,元素都是空值。ISerializable从第二个定义中删除并加载和所有工作。在加载代码上更改classstruct,并且可以看到相同的行为。就好像集合只会使用 set 访问器成功反序列化。

更新二
因此,我发现了问题(请参阅下面的答案),但我怀疑有人会通过阅读我的问题知道答案。我错过了一个重要的细节,当时我什至没有意识到这很重要。我向那些试图提供帮助的人致以最诚挚的歉意。

加载的包含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

我现在觉得有点傻,但我发现了这个问题。我已经用额外的示例代码更新了这个问题。

GetObjectData以下是代码在该方法中所做的基本操作:

  1. 加载集合
  2. 将集合复制到另一个集合

这是执行时发生的序列:

  1. 已加载集合但未加载元素
  2. 将空/默认元素复制到另一个集合并丢弃加载的集合变量
  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 回答
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 回答