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

person Steve    schedule 13.09.2010    source источник
comment
Может быть, эта тема поможет вам stackoverflow.com/questions/10225174/   -  person saramgsilva    schedule 02.10.2012


Ответы (5)


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

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

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

person Alex Paven    schedule 13.09.2010
comment
Я пробовал это, но XmlSerializer все равно не удалось, потому что «интерфейс не может быть сериализован». Я не знал этого ctor для XmlSerializer, узнал что-то новое, спасибо. - person Steve; 15.09.2010
comment
@Steve, я бы рекомендовал создать абстрактный класс под названием Animal, который реализует IAnimal. После этого вы сможете сериализовать список (при условии, что вы измените конструктор XmlSerializer, а также Cat и Dog унаследуют абстрактный класс вместо IAnimal. Var animals = new List ‹IAnimals› (); изменять не нужно. - person J. Mitchell; 28.01.2011

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

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

person Stephen Turner    schedule 26.02.2013
comment
Нет возможности сделать это в целом, не так ли? Например, если бы Age в Dog и Cat было не просто int, а вместо T, не было бы способа сделать это? - person user99999991; 15.04.2014
comment
@ user999999928 см. принятый ответ xml-serialization-of-interface-property - person nawfal; 10.07.2014
comment
Отлично. Мне очень помогли. Кроме одного. Добавление члена этого класса во всю структуру XML вызовет проблемы. Внутри метода ReadXML вы должны добавить if(reader.IsStartElement("IAnimal") перед циклом while и завершить его после строки reader.ReadEndElement();. В противном случае ваша сериализация будет завершена до того, как будет завершен весь сериализатор xml. - person blackWorX; 11.04.2016

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

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

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

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

person Aliostad    schedule 13.09.2010
comment
Я забыл упомянуть, что это должен быть текст, поэтому при необходимости я могу редактировать вручную, поэтому двоичный файл не работает. DataContractSerializer выглядит неплохо, но я огляделся и не увидел примера сериализации списка смешанного типа с его использованием. Спасибо! - person Steve; 15.09.2010

Вы можете использовать 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>
person Wojtpl2    schedule 22.09.2016

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

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

person D. Kermott    schedule 09.08.2016