注意:随着我对该问题的了解越来越多,这个问题发生了一些变化,所以请完整阅读。我决定将其保留为原始形式,因为它更好地描述了问题是如何被发现并最终解决的。
回到我们项目历史上最黑暗的深度,当我们没有真正了解C#或CLR以及我们可能的时候,我们创建了一个类型,我们称之为MyType
。我们将此类型创建为class
,即引用类型。
然而,显而易见的是MyType
应该是struct
,这是一个值类型,所以我们做了一些更改以使其成为现实并且一直都很好,直到有一天,我们尝试反序列化一些数据包含MyType
值的集合。嗯,不是真的,因为当它是引用类型时,该集合是引用的集合。现在当它反序列化时,集合反序列化很好,使用MyType
的默认构造函数,然后当实际引用反序列化时,它们是孤立的,留下我们一组空值。
所以,我们认为,“让我们在加载时将类型映射到引用类型MyTypeRef
,以便引用正确解析,然后转换回我们的实际类型,以便在执行时和重新序列化期间使用”。所以我们(使用我们自己的活页夹),但唉,它没有用,因为现在我们收到一个错误,告诉我们MyTypeRef[]
即使我们有隐式转换也无法转换为MyType[]
在MyTypeRef
和MyType
之间。
所以,我们被困住了。我们如何将序列序列化为引用类型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
答案 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
方法中所做工作的基础知识:
以下是执行时发生的序列:
请注意,只有在第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;
}