简体   繁体   English

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

[英]How do I deserialize a collection of references to a struct equivalent?

NOTE: This question changed a little as I learned more about the problem, so please read it in its entirety. 注意:随着我对这个问题的了解更多,这个问题有所改变,因此请完整阅读。 I've decided to leave it in its original form as it better describes how the problem was discovered and ultimately resolved. 我决定保留它的原始形式,因为它可以更好地描述问题是如何发现和最终解决的。


Way back in the darkest depths of our project's history when we didn't really understand C# or the CLR as well as we might've, we created a type, let's call it MyType . 当我们对C#或CLR不太了解时,回到了项目历史的最深处,我们创建了一个类型,我们将其称为MyType We created this type as a class , a reference type. 我们将此类型创建为class (引用类型)。

However, it became apparent that MyType should be a struct , a value type so we made some changes to make it so and all was well until one day, we tried to deserialize some data that contained a collection of MyType values. 但是,很明显MyType应该是一个struct ,一个值类型,因此我们做了一些更改以使它如此,直到一天之内,我们都尝试对包含MyType值集合的某些数据进行反序列化。 Well, not really, for when it was a reference type, that collection was a collection of references. 好吧,不是真的,因为当它是引用类型时,该集合就是引用的集合。 Now when it deserializes, the collection deserializes fine, using the default constructor of MyType , then later when the actual references deserialize, they are orphaned, leaving us with a collection of empty values. 现在,当它反序列化时,该集合可以使用MyType的默认构造函数进行反序列化,然后在实际引用反序列化之后,它们被孤立,从而为我们留下了一个空值集合。

So, we thought, "let's map the type to a reference type, MyTypeRef on load so that the references properly resolve, then convert back to our real type for use during execution time and reserialization". 因此,我们认为,“让我们将类型映射到引用类型, MyTypeRef在加载时将MyTypeRef映射为引用,以便引用能够正确解析,然后转换回我们的真实类型以在执行时间和重新序列化期间使用”。 So we did (using our own binder), but alas, it didn't work because now we get an error that tells us MyTypeRef[] can't be converted to MyType[] even if we have an implicit conversion between MyTypeRef and MyType . 所以我们做到了(使用我们自己的活页夹),但是,它没有用,因为现在我们收到一个错误,告诉我们即使在MyTypeRefMyType之间进行隐式转换,也无法将MyTypeRef[]转换为MyType[]

So, we're stuck. 因此,我们陷入困境。 How do we get a collection serialized as a collection of reference type MyType to deserialize as a collection of value type MyType ? 如何获得序列化为引用类型MyType的集合的序列集,反序列化为值类型MyType的集合的集合?

Update 更新
Some investigation (see comments and code below) has shown that it is the immutable nature of the new MyType and its used of ISerializable for serialization that has caused the real issue. 一些调查(请参阅下面的注释和代码)表明,新的MyType的不可变性质及其对序列化使用ISerializable导致了真正的问题。 I still don't see why that should be the case, but if I use private set accessors instead of ISerializable , the new MyType will load the old (note that the ISerializable interface is called if I use it but the collection only even contains default values). 我仍然不知道为什么会这样,但是如果我使用private集访问器而不是ISerializable ,则新的MyType将加载旧的(请注意,如果我使用ISerializable接口,则该接口将被调用,但是该集合甚至只包含默认值)值)。

Some code 一些代码

// 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");
    }
}

Note that when loading, the elements are all nulls. 请注意,加载时,所有元素均为空。 Remove ISerializable from the second definition and load and all works. 从第二个定义中删除ISerializable ,然后加载并完成所有工作。 Change class to struct on the load code and the same behaviour is visible. 更改class以对加载代码进行struct ,并且可以看到相同的行为。 It is as though the collection will only deserialize successfully using the set accessors. 好像集合只能使用set访问器成功反序列化。

Update Two 更新二
So, I found the problem (see my answer below) but I doubt anyone would've known the answer from reading my question. 因此,我发现了问题(请参阅下面的答案),但我怀疑有人会从阅读我的问题时知道答案。 I missed out an important detail that I didn't even realise was important at the time. 我错过了一个重要细节,而我当时甚至没有意识到它很重要。 My sincerest apologies to those who tried to help. 我对那些试图帮助的人表示最诚挚的歉意。

The collection that is loaded containing MyType is immediately copied to another collection during GetObjectData . 加载的包含MyType集合将在GetObjectData期间立即复制到另一个集合。 The answer below explains why this turns out to be important. 下面的答案解释了为什么这很重要。 Here is some additional sample code to that given above that should provide a complete example: 这是上面给出的一些示例代码,应提供一个完整的示例:

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);
}

Not sure to understand, but if I do this on save: 不确定是否了解,但是如果我在保存时这样做:

[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);
    }

And this on load: 而这个负载:

[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);
    }

It displays "MyTest" successfuly. 它成功显示“ MyTest”。 So what am I missing? 那我想念什么呢?

I feel somewhat foolish now but I discovered the issue. 我现在觉得有些愚蠢,但发现了问题。 I have updated the question with additional sample code. 我已经用其他示例代码更新了问题。

Here is the basics of what the code was doing in the GetObjectData method: 以下是代码在GetObjectData方法中执行的操作的基础知识:

  1. Load the collection 加载收藏
  2. Copy the collection into another collection 将集合复制到另一个集合

Here is the sequence that occurred at execution: 这是执行时发生的顺序:

  1. Collection loaded but elements are not 集合已加载,但元素未加载
  2. Copied null/default elements to another collection and discarded loaded collection variable 将null / default元素复制到另一个集合,并丢弃已加载的集合变量
  3. Elements of loaded collection are deserialized 反序列化加载的集合的元素

Note that it is only after point 3 that the entire collection is deserialized but I have already done my copy, hence having nothing. 请注意,只有在第3点之后才对整个集合进行反序列化,但是我已经完成了复制,因此一无所获。

The fix was to use the OnDeserialized attribute to specify a method to call once the entire object was deserialized. 解决方法是使用OnDeserialized属性指定在反序列化整个对象后要调用的方法。 Then, in GetObjectData I save away a reference to the collection that is loaded, but copy its contents in the OnDeserialized method once it is fully deserialized. 然后,在GetObjectData我保存了对已加载的集合的引用,但是在完全反序列化之后,将其内容复制到OnDeserialized方法中。

Side note 边注
In fact, to keep the deserialization code all in GetObjectData , I saved away a delegate to perform the copy and I call that delegate later - that way it is clear in GetObjectData exactly what will happen to finish deserialization. 实际上,为了将反序列化代码全部保留在GetObjectData ,我保存了一个委托来执行复制,然后稍后调用该委托-这样,在GetObjectData就可以清楚地知道完成反序列化的过程。

Code

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;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM