我正在尝试使用XmlSerializer来持久化List(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();
}
答案 0 :(得分:13)
您也可以使用XmlSerializer,但是您需要包含可能出现在您序列化的对象图中的所有可能类型,这会限制可扩展性并降低可维护性。你可以通过使用XmlSerializer的构造函数的重载来完成它:
var x = new XmlSerializer(animals.GetType(), new Type[] { typeof(Cat), typeof(Dog) });
此外,使用XmlSerializer时还有几个值得注意的问题,所有概述的here (MSDN) - 例如在“动态生成的程序集”标题下查看。
答案 1 :(得分:7)
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;} }
}
我考虑过将反序列化作为读者的练习,但如果没有它,代码就会非常有用。
答案 2 :(得分:3)
你必须使用XmlSerializer
吗?这是XmlSerializer
的已知问题。
您可以使用BinaryFormatter保存到流:
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, animals);
其他替代方法是使用WCF的DataContractSerializer
并使用KnownType属性提供类型。
答案 3 :(得分:3)
您可以使用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>
答案 4 :(得分:0)
简单的方法是在类中添加[Serializable()]装饰 并将您的IList更改为List,看看是否有效。
如果您使用界面,请参阅webturner的答案。