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();
}
Ответы
Ответ 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 и посмотрите, работает ли это.
Если вы используете интерфейсы, перейдите к ответу на веб-терминал.