【问题标题】:How do I deserialize a collection of references to a struct equivalent?如何反序列化对结构等效项的引用集合?
【发布时间】:2010-12-15 19:00:55
【问题描述】:

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


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

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

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

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

更新
一些调查(参见下面的 cmets 和代码)表明,新的MyType 的不可变性质以及它用于序列化的ISerializable 导致了真正的问题。我仍然不明白为什么会这样,但是如果我使用private 设置访问器而不是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 并加载,一切正常。将加载代码上的 class 更改为 struct 并且可以看到相同的行为。好像只有使用 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);
}

【问题讨论】:

  • 我想我们也可以使用活页夹将集合类型映射为我们的临时类型,最初并没有想到这一点,但后来我们不得不为所有可以想象的事情做这件事可能已与该类型一起使用的泛型,我可以看到那里出现的问题。
  • 我认为这是使用 BianryFormattet 的?嗯.. 不是一个真正的答案,但我可能会使用旧版本来阅读它,通过 XmlSerializer 将其序列化为(例如)XML,然后切换到新代码以反序列化它。然后不再使用 BF ;) 如果这是一次性操作,这完全有效。
  • 我刚刚发现重大变化不是引用类型到值类型,而是我使我的类型不可变。显然,使用ISerializable 而不是属性设置器来加载MyType 意味着它在作为集合的一部分序列化时会以不同的方式加载,从而为集合留下一个空项(如果它是引用类型,则值类型为空项)。跨度>
  • 此外,添加私有设置器并删除 ISerializable 意味着一切正常。
  • 实际上,BF 根本不使用 setter - 它应该在没有它们的情况下工作。

标签: c# .net-3.5 serialization binaryformatter


【解决方案1】:

不确定是否理解,但如果我在保存时这样做:

[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”。那我错过了什么?

【讨论】:

  • 加载时缺少 MyType 的不同定义。
  • 标题说“如何反序列化对结构等效项的引用集合?”,这似乎有效。你需要什么确切的 MyType 定义,然后它不起作用?
  • 不幸的是,这并不能解决问题(如果 StackOverflow 只是一个回答标题中问题的地方,那将是非常不同的)。我在不知不觉中遗漏了一些非常重要的信息,现在包含在问题中。如有任何混淆,我们深表歉意。
  • @Jeff - 没问题。我真的不明白你面临的问题。很高兴知道你修好了:-)
【解决方案2】:

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

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

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

这是执行时发生的顺序:

  1. 已加载集合但未加载元素
  2. 将空/默认元素复制到另一个集合并丢弃加载的集合变量
  3. 已加载集合的元素被反序列化

请注意,只有在第 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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-24
    • 2015-03-16
    • 2021-09-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多