简体   繁体   English

XmlSerializer序列化通用接口列表

[英]XmlSerializer serialize generic List of interface

I'm trying to use the XmlSerializer to persist a List(T) where T is an interface. 我正在尝试使用XmlSerializer保留List(T),其中T是接口。 The serializer does not like interfaces. 串行器不喜欢接口。 I'm curious if there is a simple way to serialize a list of heterogeneous objects easily with XmlSerializer. 我很好奇,是否有一种简单的方法可以使用XmlSerializer轻松地序列化异构对象列表。 Here's what I'm going for: 这就是我要去的地方:

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

You can use XmlSerializer as well, but you need to include all the possible types that can appear in the object graph you're serializing, which limits extensibility and lowers maintainability. 您也可以使用XmlSerializer,但是您需要包括所有可能出现在要序列化的对象图中的类型,这限制了可扩展性并降低了可维护性。 You can do it by using an overload of the constructor of XmlSerializer: 您可以通过使用XmlSerializer的构造函数的重载来实现:

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

Also, there are several issues of note when using XmlSerializer, all of the outlined here (MSDN) - for example look under the heading 'Dynamically generated assemblies'. 另外,使用XmlSerializer时,有几个注意事项, 此处概述所有这些(MSDN) ,例如,在“动态生成的程序集”标题下查找。

The XmlSerializer can't handle an interface because it doesn't know which types to create when deserialising. XmlSerializer无法处理接口,因为它在反序列化时不知道要创建哪种类型。 To get around this you need to handle that part of the serialization yourself by implementing the IXmlSerializable interface. 为了解决这个问题,您需要通过实现IXmlSerializable接口自己处理序列化的这一部分。 This allows you to record the type so you can re-create (deserialise) it. 这使您可以记录类型,以便您可以重新创建(反序列化)它。

The ListOfIAnimal class below shows how I inherited and extended the generic list List<IAnimal> to implement the required interface. 下面的ListOfIAnimal类显示了我如何继承和扩展通用列表List<IAnimal>来实现所需的接口。 I squished up your old classes adding an extra non-interface field to each so I could see that the concrete classes were getting serialised and deserialised properly. 我压缩了您的旧类,并为每个类添加了一个额外的非接口字段,以便可以看到具体的类已正确地序列化和反序列化了。

Compared to your code I'm just using the new type ListOfIAnimal in place of List<IAnimal> , the other changes are just a little refactoring. 与您的代码相比,我只是使用新类型ListOfIAnimal代替List<IAnimal> ,其他更改只是一些重构。

Its complete code, just copy it into it's own .cs file, call the first function to step through it. 它的完整代码,只需将其复制到自己的.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;} }
}

I thought about leaving deserialize as an exercise for the reader, but the code would'n be very useful without it. 我考虑过将反序列化作为练习留给读者,但没有它,代码将不会非常有用。

Do you have to use XmlSerializer ? 您必须使用XmlSerializer吗? This is a known issue with XmlSerializer . 这是XmlSerializer的已知问题。

You can use BinaryFormatter to save to a stream: 您可以使用BinaryFormatter保存到流:

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

Other alternative is to use WCF's DataContractSerializer and provide types using KnownType attribute. 另一种选择是使用WCF的DataContractSerializer并使用KnownType属性提供类型。

You can use ExtendedXmlSerializer . 您可以使用ExtendedXmlSerializer

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

Your xml will look like: 您的xml如下所示:

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

The easy way is to add the [Serializable()] decoration to your classes and change your IList to List and see if that works. 一种简单的方法是将[Serializable()]装饰添加到您的类中,然后将IList更改为List,然后查看是否可行。

If you use interfaces then go see webturner's answer. 如果您使用界面,请参阅webturner的答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM