Как десериализовать коллекцию ссылок на структурный эквивалент? - PullRequest
2 голосов
/ 15 декабря 2010

ПРИМЕЧАНИЕ: Этот вопрос немного изменился, когда я узнал больше о проблеме, поэтому, пожалуйста, прочитайте ее полностью. Я решил оставить его в первоначальном виде, так как он лучше описывает, как проблема была обнаружена и в конечном итоге решена.


Вернемся в самые темные глубины истории нашего проекта, когда мы действительно не понимали C # или CLR так, как могли бы, мы создали тип, назовем его MyType. Мы создали этот тип как class, ссылочный тип.

Однако стало очевидно, что MyType должен быть struct, типом значения, поэтому мы внесли некоторые изменения, чтобы сделать это, и все было хорошо, пока однажды мы не попытались десериализовать некоторые данные, содержащие коллекцию MyType значения. Ну, не совсем, потому что когда это был ссылочный тип, эта коллекция была коллекцией ссылок. Теперь, когда десериализуется, коллекция десериализируется нормально, используя конструктор по умолчанию MyType, потом, когда десериализуются фактические ссылки, они теряются, оставляя нам коллекцию пустых значений.

Итак, мы подумали: «давайте сопоставим тип с ссылочным типом, MyTypeRef при загрузке, чтобы ссылки правильно разрешались, а затем преобразовали обратно в наш реальный тип для использования во время выполнения и повторной сериализации». Так мы и сделали (используя наше собственное связующее), но, увы, это не сработало, потому что теперь мы получаем ошибку, которая говорит нам, что MyTypeRef[] нельзя преобразовать в MyType[], даже если у нас есть неявное преобразование между MyTypeRef и MyType.

Итак, мы застряли. Как получить коллекцию, сериализованную как коллекцию ссылочного типа MyType, чтобы десериализовать ее как коллекцию типа значения MyType?

Обновление
Некоторое исследование (см. Комментарии и код ниже) показало, что реальная проблема вызвана неизменным характером нового 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 в коде загрузки, и то же поведение будет видно. Похоже, что коллекция будет успешно десериализована только с использованием установленных методов доступа.

Обновление два
Итак, я нашел проблему (см. Мой ответ ниже), но я сомневаюсь, что кто-то знал бы ответ, прочитав мой вопрос. Я упустил важную деталь, которую я даже не осознавал, которая была важна в то время. Мои искренние извинения тем, кто пытался помочь.

Загруженная коллекция, содержащая 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);
}

Ответы [ 2 ]

2 голосов
/ 20 декабря 2010

Теперь я чувствую себя немного глупо, но обнаружил проблему.Я обновил вопрос дополнительным примером кода.

Вот основы того, что код делал в методе GetObjectData:

  1. Загрузка коллекции
  2. Скопируйте коллекцию в другую коллекцию.

Вот последовательность, которая произошла при выполнении:

  1. Коллекция загружена, но элементы не
  2. Скопировано null / defaultэлементы к другой коллекции и отброшенная переменная загруженной коллекции
  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;
}
2 голосов
/ 15 декабря 2010

Не обязательно понять, но если я сделаю это при сохранении:

[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».Так чего мне не хватает?

...