Сериализация в C# объектов, имеющих взаимные ссылки

84
04 августа 2019, 02:20

Подскажите, пожалуйста, как правильно сериализовывать/десериализовывать объекты в XML, имеющие взаимные ссылки. Например, есть классы Car и Garage. Garage содержит список объектов типа Car, при этом каждый объект Car имеет ссылку на объект типа Garage. Вот класс Garage:

using System;
using System.Collections.Generic;
namespace CarsApp
{
    [Serializable]
    public class Garage
    {
        public List<Car> cars = new List<Car>();
        public Garage() { }
        public void AddCar(Car car)
        {
            cars.Add(car);
            car.Garage = this;
        }
    }
} 

Вот класс Car:

using System;
namespace CarsApp
{
    [Serializable]
    public class Car
    {
        public string Model { get; set; } 
        public Garage Garage { get; set; }
        public Car() { }
        public Car(string model)
        {
            Model = model;
        }
    }
}

Вот код сериализации объекта типа Garage:

using System.IO;
using System.Xml.Serialization;
namespace CarsApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Car fiat = new Car("Fiat");
            Car reno = new Car("Reno");
            Garage garage = new Garage();
            garage.AddCar(fiat);
            garage.AddCar(reno);
            XmlSerializer xmlFormat = new XmlSerializer(typeof(Garage));
            using(Stream fStream = new FileStream("garage.xml", 
                FileMode.Create, FileAccess.Write, FileShare.None))
            {
                xmlFormat.Serialize(fStream, garage);
            }
        }
    }
}

В результате выполнения получаю ошибку

System.InvalidOperationException: 'Ошибка при создании документа XML.'
InnerException System.InvalidOperationException: При сериализации объекта типа CarsApp.Garage обнаружена циклическая ссылка.

В чём ошибка и как исправить?

Answer 1

Для этой цели можно использовать DataContractSerializer с настройкой DataContractSerializerSettings.

Добавьте к проекту сборку System.Runtime.Serialization.dll и откройте пространство имен System.Runtime.Serialization.

var dcss = new DataContractSerializerSettings { PreserveObjectReferences = true };
var dcs = new DataContractSerializer(typeof(Garage), dcss);
using (Stream fStream = new FileStream("test.xml",
    FileMode.Create, FileAccess.Write, FileShare.None))
{
    dcs.WriteObject(fStream, garage);
}

PreserveObjectReferences = true как раз будет сохранять ссылки на объекты. Циклические ссылки разруливаются корректно.

При чтении можно создавать сериализатор без указания настроек.

PS: аттрибут Serializable не нужен для xml-сериализаторов. Его использует BinaryFormatter. Кстати, этот форматтер тоже корректно обрабатывает циклические ссылки - можно использовать его.

PPS: при наличии атрибута Serializable у класса Car генерируется xml следующего вида:

<Car z:Id="3">
  <_x003C_Garage_x003E_k__BackingField z:Ref="1" i:nil="true"/>
  <_x003C_Model_x003E_k__BackingField z:Id="4">Fiat</_x003C_Model_x003E_k__BackingField>
</Car>

Без этого атрибута xml выглядит намного приятней:

<Car z:Id="3">
  <Garage z:Ref="1" i:nil="true"/>
  <Model z:Id="4">Fiat</Model>
</Car>

То есть DataContractSerializer все-таки как-то учитывает этот атрибут.

Answer 2

Если использовать бинарную сериализацию вместо сериализации в XML то можно хранить любые данные лобой сложности вложенности

К классу который будем сериализовать добавляем

[Serializable]
public class SomeItem
{}

Враппер на сериализацию:

public static class Serializator
{
    private static BinaryFormatter _bin = new BinaryFormatter();
    public static void Serialize(string pathOrFileName, object objToSerialise)
    {
        using (Stream stream = File.Open(pathOrFileName, FileMode.Create))
        {
            try 
            {
                _bin.Serialize(stream, objToSerialise);
            }
            catch (SerializationException e) 
            {
                Console.WriteLine("Failed to serialize. Reason: " + e.Message);
                throw;
            }
        }
    }
    public static T Deserialize<T>(string pathOrFileName) 
    {
        T items;
        using (Stream stream = File.Open(pathOrFileName, FileMode.Open))
        {
            try 
            {
                items = (T) _bin.Deserialize(stream);
            }
            catch (SerializationException e) 
            {
                Console.WriteLine("Failed to deserialize. Reason: " + e.Message);
                throw;
            }
        }
        return items;
    }
}

ну и, собственно, сам пример использования:

List<SomeItem> itemsCollected;//list with some data
Serializator.Serialize("data.dat", itemsCollected);
var a = Serializator.Deserialize<List<SomeItem>>("data.dat");

В случае ошибки будет выдавать в консоль сообщение о исключении сериализации.

READ ALSO
Запрос данных из двух таблиц и вывод через while

Запрос данных из двух таблиц и вывод через while

Есть две таблицы comments и usersОни связаны между собой

127
Contact form 7 добавление кода PHP

Contact form 7 добавление кода PHP

На сайте под управлением Wordpress есть форма сделана c помощью плагина Contact Form 7Как добавить во входящий имейл из формы еще функцию get_page_link() Мне...

113
Ошибка компиляции кода из книги Р.Лафоре

Ошибка компиляции кода из книги Р.Лафоре

Учу язык С++ по книги РЛафоре "Объектно-ориентированное программирование на С++"

86