XmlSerializer сериализует общий список интерфейса - PullRequest
12 голосов
/ 14 сентября 2010

Я пытаюсь использовать 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();    
    }

Ответы [ 5 ]

13 голосов
/ 14 сентября 2010

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

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

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

7 голосов
/ 26 февраля 2013

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 голосов
/ 14 сентября 2010

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

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

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

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

2 голосов
/ 22 сентября 2016

Вы можете использовать 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>
0 голосов
/ 09 августа 2016

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

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

...