Подтвердить что ты не робот

XmlSerializer сериализует общий список интерфейса

Я пытаюсь использовать XmlSerializer для сохранения списка (T), где T является интерфейсом. Сериализатор не любит интерфейсы. Мне любопытно, есть ли простой способ сериализовать список гетерогенных объектов с помощью XmlSerializer. Вот что я собираюсь сделать:

    public interface IAnimal
    {
        int Age();
    }
    public class Dog : IAnimal
    {
        public int Age()
        {
            return 1;
        }
    }
    public class Cat : IAnimal
    {
        public int Age()
        {
            return 1;
        }
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        var animals = new List<IAnimal>
        {
            new Dog(),
            new Cat()
        };

        var x = new XmlSerializer(animals.GetType());
        var b = new StringBuilder();
        var w = XmlTextWriter.Create(b, new XmlWriterSettings { NewLineChars = "\r\n", Indent = true });
        //FAIL - cannot serialize interface. Does easy way to do this exist?
        x.Serialize(w, animals);
        var s = b.ToString();    
    }
4b9b3361

Ответ 1

Вы также можете использовать XmlSerializer, но вам нужно включить все возможные типы, которые могут отображаться в графе объектов, которые вы сериализуете, что ограничивает расширяемость и снижает ремонтопригодность. Вы можете сделать это, используя перегрузку конструктора XmlSerializer:

var x = new XmlSerializer(animals.GetType(), new Type[] { typeof(Cat), typeof(Dog) });

Кроме того, есть несколько вопросов, которые следует учитывать при использовании XmlSerializer, все описанные здесь (MSDN) - например, посмотрите под заголовком ' Динамически сгенерированные сборки.

Ответ 2

XmlSerializer не может обрабатывать интерфейс, поскольку он не знает, какие типы следует создавать при десериализации. Чтобы обойти это, вам нужно самому обработать эту часть сериализации, реализовав интерфейс IXmlSerializable. Это позволяет записывать тип, чтобы вы могли повторно создать (десериализировать) его.

В приведенном ниже классе ListOfIAnimal показано, как я унаследовал и расширил общий список List<IAnimal>, чтобы реализовать требуемый интерфейс. Я сдавил ваши старые классы, добавляя к ним дополнительное поле без интерфейса, поэтому я мог видеть, что конкретные классы стали сериализоваться и десериализоваться должным образом.

По сравнению с вашим кодом я просто использую новый тип ListOfIAnimal вместо List<IAnimal>, другие изменения - это всего лишь небольшой рефакторинг.

Его полный код, просто скопируйте его в собственный файл .cs, вызовите первую функцию, чтобы пройти через нее.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace Serialiser
{
    static class SerialiseInterface
    {
        public static void SerialiseAnimals()
        {
            String finalXml;

            // Serialize
            {
                var animals = new ListOfIAnimal{
                    new Dog() { Age = 5, Teeth = 30 },
                    new Cat() { Age = 6, Paws = 4 }
                };

                var xmlSerializer = new XmlSerializer(animals.GetType());
                var stringBuilder = new StringBuilder();
                var xmlTextWriter = XmlTextWriter.Create(stringBuilder, new XmlWriterSettings { NewLineChars = "\r\n", Indent = true });
                xmlSerializer.Serialize(xmlTextWriter, animals);
                finalXml = stringBuilder.ToString();
            }

            // Deserialise
            {
                var xmlSerializer = new XmlSerializer(typeof(ListOfIAnimal));
                var xmlReader = XmlReader.Create(new StringReader(finalXml));
                ListOfIAnimal animals = (ListOfIAnimal)xmlSerializer.Deserialize(xmlReader);
            }
        }
    }

    public class ListOfIAnimal : List<IAnimal>, IXmlSerializable
    {
        public ListOfIAnimal() : base() { }

        #region IXmlSerializable
        public System.Xml.Schema.XmlSchema GetSchema() { return null; }

        public void ReadXml(XmlReader reader)
        {
            reader.ReadStartElement("ListOfIAnimal");
            while (reader.IsStartElement("IAnimal"))
            {
                Type type = Type.GetType(reader.GetAttribute("AssemblyQualifiedName"));
                XmlSerializer serial = new XmlSerializer(type);

                reader.ReadStartElement("IAnimal");
                this.Add((IAnimal)serial.Deserialize(reader));
                reader.ReadEndElement(); //IAnimal
            }
            reader.ReadEndElement(); //ListOfIAnimal
        }

        public void WriteXml(XmlWriter writer)
        {
            foreach (IAnimal animal in this)
            {
                writer.WriteStartElement("IAnimal");
                writer.WriteAttributeString("AssemblyQualifiedName", animal.GetType().AssemblyQualifiedName);
                XmlSerializer xmlSerializer = new XmlSerializer(animal.GetType());
                xmlSerializer.Serialize(writer, animal);
                writer.WriteEndElement();
            }
        }
        #endregion
    }

    public interface IAnimal { int Age { get; set; } }
    public class Dog : IAnimal { public int Age { get; set;} public int Teeth { get; set;} }
    public class Cat : IAnimal { public int Age { get; set;} public int Paws { get; set;} }
}

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

Ответ 3

Вам нужно использовать XmlSerializer? Это известная проблема с XmlSerializer.

Вы можете использовать BinaryFormatter для сохранения в потоке:

BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, animals);

Другой альтернативой является использование WCF DataContractSerializer и предоставление типов с использованием атрибута KnownType.

Ответ 4

Вы можете использовать ExtendedXmlSerializer.

var serializer = new ExtendedXmlSerializer();
var xml = serializer.Serialize(animals);

Ваш xml будет выглядеть так:

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfIAnimal>
    <Dog type="Model.Dog" />
    <Cat type="Model.Cat" />
</ArrayOfIAnimal>

Ответ 5

Простым способом является добавление декорации [Serializable()] к вашим классам и измените свой IList на List и посмотрите, работает ли это.

Если вы используете интерфейсы, перейдите к ответу на веб-терминал.