【问题标题】:A type of generic list deserialization class?一种通用列表反序列化类?
【发布时间】:2009-09-25 14:12:51
【问题描述】:

好的,到此为止。

我已经可以使用XmlSerializer 反序列化单个对象,但是反序列化列表被证明是一个真正令人头疼的问题。我首先尝试序列化List<Foo>,并且序列化程序在根<ArrayOfFoo> 元素内序列化多个<Foo> XML 结构。事实证明,反序列化是有问题的,所以看起来我需要自己定义 'ArrayOfFoo' 元素。所以,我有一个类在工作,它是列表的“包装器”,如以下程序所示:

using System;
using System.IO;
using System.Collections.Generic;
using System.Xml.Serialization;

namespace XmlTester2
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("XML tester...");

            string xml =
                "<ItemList xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
                "<Person i:type=\"PersonI2\">" + "<Field1>field1Val</Field1>" +
                "<Field2>field2Val</Field2>" + "<Field3>field3Val</Field3>" +
                "<Field4>field4Val</Field4>" + "</Person>" +
                "<Account i:type=\"AccountI2\">" + "<Field1>field1Val</Field1>" +
                "<Field2>field2Val</Field2>" + "<Field3>field3Val</Field3>" +
                "<Field4>field4Val</Field4>" + "</Account>" +
                "<Person i:type=\"PersonI2\">" + "<Field1>field1Val</Field1>" +
                "<Field2>field2Val</Field2>" + "<Field3>field3Val</Field3>" +
                "<Field4>field4Val</Field4>" + "</Person>" + "</ItemList>";

            XmlSerializer ser = new XmlSerializer(typeof(ItemList));

            using (var reader = new StringReader(xml))
            {
                ItemList result = (ItemList)ser.Deserialize(reader);
            }

            Console.WriteLine("Break here and check 'result' in Quickwatch...");
            Console.ReadKey();
        }
    }

    [XmlRootAttribute(IsNullable = false)]
    public class ItemList
    {
        [XmlElementAttribute("Person")]
        public List<Person> Persons { get; set; }

        [XmlElementAttribute("Account")]
        public List<Account> Accounts { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "Person", Namespace = "")]
    [XmlInclude(typeof(PersonI2))]
    public class Person
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "PersonI2", Namespace = "")]
    public class PersonI2 : Person
    {
        public string Field4 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "Account", Namespace = "")]
    [XmlInclude(typeof(AccountI2))]
    public class Account
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "AccountI2", Namespace = "")]
    public class AccountI2 : Account
    {
        public string Field4 { get; set; }
    }
}

但是,这个“包装器”ItemList 仍然必须在其中手动定义所有可能包含的元素(在示例中,Person 和 Account)。真正理想的是拥有一个通用的列表包装类。我知道这有点希望,但有办法做到这一点吗?我正在考虑这些方面的东西(这不起作用,只是为了给你一个大致的想法):

using System;
using System.IO;
using System.Collections.Generic;
using System.Xml.Serialization;

namespace XmlTester3
{
    public class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("XML tester...");

            string xml =
                "<ItemList xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">" +
                "<Person i:type=\"PersonI2\">" + 
                "<Field1>field1Val</Field1>" +
                "<Field2>field2Val</Field2>" + 
                "<Field3>field3Val</Field3>" +
                "<Field4>field4Val</Field4>" + 
                "</Person>" +
                "<Person i:type=\"PersonI2\">" + 
                "<Field1>field1Val</Field1>" +
                "<Field2>field2Val</Field2>" + 
                "<Field3>field3Val</Field3>" +
                "<Field4>field4Val</Field4>" + 
                "</Person>" + 
                "</ItemList>";

            XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));

            using (var reader = new StringReader(xml))
            {
                ItemList<Person> result = (ItemList<Person>)ser.Deserialize(reader);
            }

            Console.WriteLine("Break here and check 'result' in Quickwatch...");
            Console.ReadKey();
        }
    }

    [XmlRootAttribute(IsNullable = false)]
    [XmlInclude(typeof(Person))]
    [XmlInclude(typeof(PersonI2))]
    [XmlInclude(typeof(Account))]
    [XmlInclude(typeof(AccountI2))]
    public class ItemList<T>
    {
        [XmlElementAttribute]
        public List<T> Items { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "Person", Namespace = "")]
    [XmlInclude(typeof(PersonI2))]
    public class Person
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "PersonI2", Namespace = "")]
    public class PersonI2 : Person
    {
        public string Field4 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "Account", Namespace = "")]
    [XmlInclude(typeof(AccountI2))]
    public class Account
    {
        public string Field1 { get; set; }
        public string Field2 { get; set; }
        public string Field3 { get; set; }
    }

    [XmlTypeAttribute(AnonymousType = false, TypeName = "AccountI2", Namespace = "")]
    public class AccountI2 : Account
    {
        public string Field4 { get; set; }
    }
}

因此,在 ItemList 中传递的 XML 结构只能是一种类型,例如本例中的 Person,我可以定义一个 ItemList&lt;Person&gt;,它允许我反序列化包含多个人对象?有任何想法吗?如有必要,我不介意为ItemList 可能包含的每种类型都使用[XmlInclude...] 标记ItemList 类。

我猜这是可能的,我只是还没弄清楚怎么做? :-) 还是默认的 XmlSerializer 太挑剔了?

【问题讨论】:

  • 从 ArrayOfFoo 元素反序列化到 List 应该可以工作(我只是在一个基本的测试应用程序中做到了) - 有什么问题?

标签: c# serialization xml-serialization


【解决方案1】:

你可以很容易地做到这一点,只需实现System.Xml.Serialization.IXmlSerializable 接口。如果我这样做,我什至可能会在定义 T 的程序集中反映 T 的可能派生类型,并完全省略 [XmlInclude] 声明。这种方法的真正缺点是创建了 XmlSerializers。您可能会考虑缓存它们。无论如何,只需在第二个示例中使用它,它应该可以工作。

顺便说一句,你用 "i:type=\"PersonI2\""; 做的事情很有趣。解决这个问题的道具;)

[XmlRootAttribute("ItemList", IsNullable = false)]
[XmlInclude(typeof(Person))]
[XmlInclude(typeof(PersonI2))]
[XmlInclude(typeof(Account))]
[XmlInclude(typeof(AccountI2))]
public class ItemList<T> : System.Xml.Serialization.IXmlSerializable
{
    class Map : Dictionary<String, XmlSerializer> 
    { public Map() : base(StringComparer.Ordinal) { } }

    public List<T> Items { get; set; }

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    private string TypeName(Type t)
    {
        String typeName = t.Name;
        foreach (XmlTypeAttribute a in t.GetCustomAttributes(typeof(XmlTypeAttribute), true))
            if (!String.IsNullOrEmpty(a.TypeName))
                typeName = a.TypeName;
        return typeName;
    }

    private Map LoadSchema()
    {
        Map map = new Map();
        foreach (XmlIncludeAttribute inc in typeof(ItemList<T>).GetCustomAttributes(typeof(XmlIncludeAttribute), true))
        {
            Type t = inc.Type;
            if (typeof(T).IsAssignableFrom(t))
                map.Add(TypeName(t), new XmlSerializer(t));
        }
        return map;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        Map map = LoadSchema();
        int depth = reader.Depth;

        List<T> items = new List<T>();
        if (!reader.IsEmptyElement && reader.Read())
        {
            while (reader.Depth > depth)
            {
                items.Add((T)map[reader.LocalName].Deserialize(reader));
            }
        }
        this.Items = items;
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        Map map = LoadSchema();
        foreach (T item in this.Items)
        {
            map[TypeName(item.GetType())].Serialize(writer, item);
        }
    }
}

【讨论】:

    【解决方案2】:

    我不确定我是否理解你的问题,但你知道有一个XmlArrayItemAttribute

    [XmlArray("foos"), XmlArrayItem(typeof(Foo), ElementName = "foo")]
    

    【讨论】:

    • 问题在于你必须硬编码 typeof(Foo) 和相关的元素名称,所以它不能是通用的。
    【解决方案3】:

    在 .NET 3.5 SP1(特别是 SP1)下,您可以使用 WCF 中的序列化程序来反序列化对象,而无需使用 DataContract 或 Serializable 属性专门标记类。

    几乎任何类都应该能够以这种方式反序列化 - 只要属性名称与元素名称匹配。

    如果您遇到反序列化程序错误 - 那可能是因为某些属性名称错误或类型不正确。要检查 Serializer 正在寻找的输入,您可以填充一次对象,然后将其序列化为 XML 以进行比较。

    不久前我为自己编写了一个帮助类来使用它。

    helper的使用方法是:

    string serialized = "some xml";
    MyType foo = Helpers.Deserialize<MyType>(serialized, SerializerType.Xml); 
    

    实际的帮助类:

    using System.IO;
    using System.Runtime.Serialization; // System.Runtime.Serialization.dll (.NET 3.0)
    using System.Runtime.Serialization.Json; // System.ServiceModel.Web.dll (.NET 3.5)
    using System.Text;
    namespace Serialization
    {
        public static class Helpers
        {
            /// <summary>
            /// Declare the Serializer Type you want to use.
            /// </summary>
            public enum SerializerType
            {
                Xml, // Use DataContractSerializer
                Json // Use DataContractJsonSerializer
            }
    
            public static T Deserialize<T>(string SerializedString, SerializerType UseSerializer)
            {
                // Get a Stream representation of the string.
                using (Stream s = new MemoryStream(UTF8Encoding.UTF8.GetBytes(SerializedString)))
                {
                    T item;
                    switch (UseSerializer)
                    {
                        case SerializerType.Json:
                            // Declare Serializer with the Type we're dealing with.
                            var serJson = new DataContractJsonSerializer(typeof(T));
                            // Read(Deserialize) with Serializer and cast
                            item = (T)serJson.ReadObject(s);
                            break;
                        case SerializerType.Xml:
                        default:
                            var serXml = new DataContractSerializer(typeof(T));
                            item = (T)serXml.ReadObject(s);
                            break;
                    }
                    return item;
                }
            }
    
            public static string Serialize<T>(T ObjectToSerialize, SerializerType UseSerializer)
            {
                using (MemoryStream serialiserStream = new MemoryStream())
                {
                    string serialisedString = null;
                    switch (UseSerializer)
                    {
                        case SerializerType.Json:
                            // init the Serializer with the Type to Serialize
                            DataContractJsonSerializer serJson = new DataContractJsonSerializer(typeof(T));
                            // The serializer fills the Stream with the Object's Serialized Representation.
                            serJson.WriteObject(serialiserStream, ObjectToSerialize);
                            break;
                        case SerializerType.Xml:
                        default:
                            DataContractSerializer serXml = new DataContractSerializer(typeof(T));
                            serXml.WriteObject(serialiserStream, ObjectToSerialize);
                            break;
                    }
                    // Rewind the stream to the start so we can now read it.
                    serialiserStream.Position = 0;
                    using (StreamReader sr = new StreamReader(serialiserStream))
                    {
                        // Use the StreamReader to get the serialized text out
                        serialisedString = sr.ReadToEnd();
                        sr.Close();
                    }
                    return serialisedString;
                }
            }
        }
    }
    

    【讨论】:

    • 这只是问题所在;如果我想要一个通用的 ItemList 包装类,允许我序列化/反序列化通用项目的列表,我不知道反序列化时需要在 ItemList 中的属性的名称 - 它们会根据已序列化的类型而有所不同.我需要一种通用的方法来改变反序列化器的期望……例如。如果我创建一个 ItemList,它需要 [...]
    • 啊,我误会了。我以为你已经提前知道了这些属性并且有一个类可以反序列化。如果您在编译时不知道属性,那么在 .NET 4.0 之前,您需要通过字典或 xpath 访问属性。
    【解决方案4】:

    (反)序列化对象有两种主要技术:

    1. 为您想要(反)序列化的每个类实现一个接口及其 Serialize() 和 Deserialize() 方法 - 速度快但需要大量维护。

    2. 使用基于反射的 serizlier/deserializer 分析类中的公共字段和属性 - 速度较慢,但​​不需要在每个类中维护 (de)serialize() 方法。

    就个人而言,在很多情况下,我更喜欢第二种技术。

    .NET 内置的 XmlSerializer 支持第二种技术,但有很多限制:

    1.多维度数组。

    2.反序列化意外类型的对象:

    公共我的班级 { 公共 IMyInterface MyProperty1 { 得到; 放; } 公共 MyBaseType MyProperty2 { 得到; 放; } }

    反序列化过程中,MyProperty1、MyProperty2 中实际对象的类型是未知的。
    3. (反)序列化复杂集合。

    4.没有很好的方法来处理在序列化和反序列化之间向类添加/删除字段/属性的情况。

    5.不支持用循环序列化图形。


    我想出的解决方案是编写一个基于反射的自定义序列化器/反序列化器, 当时我找不到任何现有的序列化程序,所以我从头开始编写了一个新的序列化程序。 我无法发布它,因为它是专有的,但是我注意到后来发布了模拟序列化程序:

    http://www.codeproject.com/KB/XML/GR_CustomXmlSerializer.aspx
    XML Serialization and Inherited Types
    http://www.codeproject.com/KB/XML/deepserializer.aspx

    【讨论】:

      【解决方案5】:

      !这是我找到的最佳解决方案!

      好的,对于这里的垃圾邮件回答抱歉,人们,但我发现了一种更优雅的方法,可以避免 ItemList 需要使用“Items”属性访问其项目;使 ItemList 成为 List 本身!这样,您只需将 ItemList 作为列表直接访问。这是修改后的示例程序:

      using System;
      using System.IO;
      using System.Text;
      using System.Collections.Generic;
      using System.Xml;
      using System.Xml.Schema;
      using System.Xml.Serialization;
      
      namespace XmlTester
      {
          public class Program {
              static void Main(string[] args) {
                  Console.WriteLine("XML tester...");
      
      // Valid XML for an ItemList of Person's
      XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
      string xmlIn =
      @"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
          <PersonBilingual>
              <FullName>John Smith</FullName>
              <Age>21</Age>
              <Language>French</Language>
              <SecondLanguage>German</SecondLanguage>
          </PersonBilingual>
          <Person>
              <FullName>Joe Bloggs</FullName>
              <Age>26</Age>
              <Language>English</Language>
          </Person>
          <Person i:type=""PersonBilingual"">
              <FullName>Jane Doe</FullName>
              <Age>78</Age>
              <Language>Italian</Language>
              <SecondLanguage>English</SecondLanguage>
          </Person>
      </ItemList>";
      
      //// Valid XML for an ItemList of Account's
      //XmlSerializer ser = new XmlSerializer(typeof(ItemList<Account>));
      //string xmlIn =
      //@"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
      //  <AccountBank>
      //      <AcctName>Deposit account</AcctName>
      //      <WithCompany>Bank of Switzerland</WithCompany>
      //      <BalanceInEuros>300</BalanceInEuros>
      //  </AccountBank>
      //  <Account>
      //      <AcctName>Book buying account</AcctName>
      //      <WithCompany>Amazon</WithCompany>
      //  </Account>
      //  <Account i:type=""AccountBank"">
      //      <AcctName>Savings account</AcctName>
      //      <WithCompany>Bank of America</WithCompany>
      //      <BalanceInEuros>2500</BalanceInEuros>
      //  </Account>
      //</ItemList>";
      
      //// Invalid XML, as we have mixed incompatible types
      //XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
      //string xmlIn =
      //@"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
      //  <PersonBilingual>
      //      <FullName>John Smith</FullName>
      //      <Age>21</Age>
      //      <Language>French</Language>
      //      <SecondLanguage>German</SecondLanguage>
      //  </PersonBilingual>
      //  <Account>
      //      <AcctName>Book buying account</AcctName>
      //      <WithCompany>Amazon</WithCompany>
      //  </Account>
      //  <Person i:type=""PersonBilingual"">
      //      <FullName>Jane Doe</FullName>
      //      <Age>78</Age>
      //      <Language>Italian</Language>
      //      <SecondLanguage>English</SecondLanguage>
      //  </Person>
      //</ItemList>";
      
                  // Deserialize...
                  ItemList<Person> result;
                  using (var reader = new StringReader(xmlIn)) {
                      result = (ItemList<Person>)ser.Deserialize(reader);
                  }
      
                  Console.WriteLine("Break here and check 'result' in Quickwatch...");
                  Console.ReadKey();
      
                  // Serialize...
                  StringBuilder xmlOut = new StringBuilder();
                  ser.Serialize(new StringWriter(xmlOut), result);
      
                  Console.WriteLine("Break here and check 'xmlOut' in Quickwatch...");
                  Console.ReadKey();
              }
          }
      
          [XmlRoot(ElementName = "ItemList", IsNullable = false)]
          [XmlInclude(typeof(Person))]
          [XmlInclude(typeof(PersonBilingual))]
          [XmlInclude(typeof(Account))]
          [XmlInclude(typeof(AccountBank))]
          public class ItemList<T> : List<T>, IXmlSerializable {
              #region Private vars
      
              /// <summary>
              /// The class that will store our serializers for the various classes that may be (de)serialized, given
              /// the type of this ItemList (ie. the type itself, as well as any type that extends the type)
              /// </summary>
              private class Map : Dictionary<string, XmlSerializer> { public Map() : base(StringComparer.Ordinal) { } }
      
              #endregion
      
              #region Private methods
      
              /// <summary>
              /// Creates a 'schema' for this ItemList, using its type, and the XmlIncludeAttribute types that are
              /// associated with it.  For each XmlIncludeAttribute, if it can be assigned to this ItemList's type (so
              /// it's either the same type as this ItemList's type or a type that extends this ItemList's type), adds
              /// the XmlSerializer for that XmlIncludeAttribute's type to our 'schema' collection, allowing a node
              /// corresponding to that type to be (de)serialized by this ItemList.
              /// </summary>
              /// <returns>The 'schema' containing the XmlSerializer's available for this ItemList to use during (de)serialization.</returns>
              private Map loadSchema() {
                  Map map = new Map();
                  foreach (XmlIncludeAttribute inc in typeof(ItemList<T>).GetCustomAttributes(typeof(XmlIncludeAttribute), true)) {
                      Type t = inc.Type;
                      if (typeof(T).IsAssignableFrom(t)) { map.Add(xmlTypeName(t), new XmlSerializer(t)); }
                  }
                  return map;
              }
      
              /// <summary>
              /// As the XML type name can be different to our internal class name for that XML type, we need to be able
              /// to expect an XML element name that is different to our internal class name for that XML type.  Hence,
              /// our 'schema' map will contain XmlSerializer's whose keys are based on the XML type name, NOT our
              /// internal class name for that XML type.  This method returns the XML type name given our internal
              /// class we're using to (de)serialize that XML type.  If no XML TypeName is specified in our internal
              /// class's XmlTypeAttribute, we assume an XML type name identical to the internal class name.
              /// </summary>
              /// <param name="t">Our internal class used to (de)serialize an XML type.</param>
              /// <returns>The XML type name corresponding to the given internal class.</returns>
              private string xmlTypeName(Type t) {
                  string typeName = t.Name;
                  foreach (XmlTypeAttribute ta in t.GetCustomAttributes(typeof(XmlTypeAttribute), true)) {
                      if (!string.IsNullOrEmpty(ta.TypeName)) { typeName = ta.TypeName; }
                  }
                  return typeName;
              }
      
              #endregion
      
              #region IXmlSerializable Members
      
              /// <summary>
              /// Reserved and should not be used.
              /// </summary>
              /// <returns>Must return null.</returns>
              public XmlSchema GetSchema() {
                  return null;
              }
      
              /// <summary>
              /// Generates a list of type T objects from their XML representation; stores them in this ItemList.
              /// </summary>
              /// <param name="reader">The System.Xml.XmlReader stream from which the objects are deserialized.</param>
              public void ReadXml(XmlReader reader) {
                  Map map = loadSchema();
                  int depth = reader.Depth;
      
                  List<T> items = new List<T>();
                  if (!reader.IsEmptyElement && reader.Read()) {
                      // While the reader is at a greater depth that the initial depth, ie. at one of the elements
                      // inside the list wrapper, the initial depth being that of the list wrapper <ItemList>...
                      while (reader.Depth > depth) {
                          try { items.Add((T)map[reader.LocalName].Deserialize(reader)); }
                          catch (InvalidOperationException iopEx) {
                              if (
                                  iopEx.InnerException != null &&
                                  iopEx.InnerException.Message.StartsWith("The specified type was not recognized")
                              ) { throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because although its element node is a valid type, its attribute-specified type was not recognized.  Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", iopEx); }
                          }
                          catch (KeyNotFoundException knfEx) {
                              throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because its element node was not recognized as a valid type.  Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", knfEx);
                          }
                          catch (Exception ex) {
                              throw ex;
                          }
                      }
                  }
                  this.AddRange(items);
              }
      
              /// <summary>
              /// Converts a list of type T objects into their XML representation; writes them to the specified writer.
              /// </summary>
              /// <param name="writer">The System.Xml.XmlWriter stream to which the objects are serialized.</param>
              public void WriteXml(XmlWriter writer) {
                  Map map = loadSchema();
                  foreach (T item in this) {
                      map[xmlTypeName(item.GetType())].Serialize(writer, item);
                  }
              }
      
              #endregion
          }
      
          /// <summary>
          /// A regular person.
          /// </summary>
          [XmlType(AnonymousType = false, TypeName = "Person", Namespace = "")]
          [XmlInclude(typeof(PersonBilingual))]
          public class Person {
              public string FullName { get; set; }
              public int Age { get; set; }
              public string Language { get; set; }
          }
      
          /// <summary>
          /// A person who can speak a second language.
          /// </summary>
          [XmlType(AnonymousType = false, TypeName = "PersonBilingual", Namespace = "")]
          public class PersonBilingual : Person {
              public string SecondLanguage { get; set; }
          }
      
          /// <summary>
          /// Some kind of account.
          /// </summary>
          [XmlType(AnonymousType = false, TypeName = "Account", Namespace = "")]
          [XmlInclude(typeof(AccountBank))]
          public class Account {
              public string AcctName { get; set; }
              public string WithCompany { get; set; }
          }
      
          /// <summary>
          /// A bank account.
          /// </summary>
          [XmlType(AnonymousType = false, TypeName = "AccountBank", Namespace = "")]
          public class AccountBank : Account {
              public int BalanceInEuros { get; set; }
          }
      }
      

      【讨论】:

        【解决方案6】:

        更新:请参阅开头的答案!这是我找到的最佳解决方案! - 这是一个比这个更好的解决方案。

        ...

        受到 csharptest.net 评论的极大启发,我创建了一个几乎可以完成我想要的工作的类。 :-) 您通过检查 ItemList.Items 来访问反序列化的项目,并通过将项目插入 ItemList.Items 来序列化内容,然后使用适当的 XmlSerializer 对其进行序列化。唯一的小烦恼是,您必须确保 ItemList 类为每个可能需要(反)序列化的类类型标记有 XmlIncludeAttribute,否则 XmlSerializer 将无法处理它。

        这是示例程序,包含通用的 ItemList 类:

        using System;
        using System.IO;
        using System.Text;
        using System.Collections.Generic;
        using System.Xml;
        using System.Xml.Schema;
        using System.Xml.Serialization;
        
        namespace XmlTester
        {
         public class Program {
          static void Main(string[] args) {
           Console.WriteLine("XML tester...");
        
        // Valid XML for an ItemList of Person's
        XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
        string xmlIn =
        @"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
         <PersonBilingual>
          <FullName>John Smith</FullName>
          <Age>21</Age>
          <Language>French</Language>
          <SecondLanguage>German</SecondLanguage>
         </PersonBilingual>
         <Person>
          <FullName>Joe Bloggs</FullName>
          <Age>26</Age>
          <Language>English</Language>
         </Person>
         <Person i:type=""PersonBilingual"">
          <FullName>Jane Doe</FullName>
          <Age>78</Age>
          <Language>Italian</Language>
          <SecondLanguage>English</SecondLanguage>
         </Person>
        </ItemList>";
        
        //// Valid XML for an ItemList of Account's
        //XmlSerializer ser = new XmlSerializer(typeof(ItemList<Account>));
        //string xmlIn =
        //@"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
        // <AccountBank>
        //  <AcctName>Deposit account</AcctName>
        //  <WithCompany>Bank of Switzerland</WithCompany>
        //  <BalanceInEuros>300</BalanceInEuros>
        // </AccountBank>
        // <Account>
        //  <AcctName>Book buying account</AcctName>
        //  <WithCompany>Amazon</WithCompany>
        // </Account>
        // <Account i:type=""AccountBank"">
        //  <AcctName>Savings account</AcctName>
        //  <WithCompany>Bank of America</WithCompany>
        //  <BalanceInEuros>2500</BalanceInEuros>
        // </Account>
        //</ItemList>";
        
        //// Invalid XML, as we have mixed incompatible types
        //XmlSerializer ser = new XmlSerializer(typeof(ItemList<Person>));
        //string xmlIn =
        //@"<ItemList xmlns:i=""http://www.w3.org/2001/XMLSchema-instance"">
        // <PersonBilingual>
        //  <FullName>John Smith</FullName>
        //  <Age>21</Age>
        //  <Language>French</Language>
        //  <SecondLanguage>German</SecondLanguage>
        // </PersonBilingual>
        // <Account>
        //  <AcctName>Book buying account</AcctName>
        //  <WithCompany>Amazon</WithCompany>
        // </Account>
        // <Person i:type=""PersonBilingual"">
        //  <FullName>Jane Doe</FullName>
        //  <Age>78</Age>
        //  <Language>Italian</Language>
        //  <SecondLanguage>English</SecondLanguage>
        // </Person>
        //</ItemList>";
        
           // Deserialize...
           ItemList<Person> result;
           using (var reader = new StringReader(xmlIn)) {
            result = (ItemList<Person>)ser.Deserialize(reader);
           }
        
           Console.WriteLine("Break here and check 'result' in Quickwatch...");
           Console.ReadKey();
        
           // Serialize...
           StringBuilder xmlOut = new StringBuilder();
           ser.Serialize(new StringWriter(xmlOut), result);
        
           Console.WriteLine("Break here and check 'xmlOut' in Quickwatch...");
           Console.ReadKey();
          }
         }
        
         [XmlRoot(ElementName = "ItemList", IsNullable = false)]
         [XmlInclude(typeof(Person))]
         [XmlInclude(typeof(PersonBilingual))]
         [XmlInclude(typeof(Account))]
         [XmlInclude(typeof(AccountBank))]
         public class ItemList<T> : IXmlSerializable {
          #region Private vars
        
          /// <summary>
          /// The class that will store our serializers for the various classes that may be (de)serialized, given
          /// the type of this ItemList (ie. the type itself, as well as any type that extends the type)
          /// </summary>
          private class Map : Dictionary<string, XmlSerializer> { public Map() : base(StringComparer.Ordinal) { } }
        
          #endregion
        
          #region Private methods
        
          /// <summary>
          /// Creates a 'schema' for this ItemList, using its type, and the XmlIncludeAttribute types that are
          /// associated with it.  For each XmlIncludeAttribute, if it can be assigned to this ItemList's type (so
          /// it's either the same type as this ItemList's type or a type that extends this ItemList's type), adds
          /// the XmlSerializer for that XmlIncludeAttribute's type to our 'schema' collection, allowing a node
          /// corresponding to that type to be (de)serialized by this ItemList.
          /// </summary>
          /// <returns>The 'schema' containing the XmlSerializer's available for this ItemList to use during (de)serialization.</returns>
          private Map loadSchema() {
           Map map = new Map();
           foreach (XmlIncludeAttribute inc in typeof(ItemList<T>).GetCustomAttributes(typeof(XmlIncludeAttribute), true)) {
            Type t = inc.Type;
            if (typeof(T).IsAssignableFrom(t)) { map.Add(xmlTypeName(t), new XmlSerializer(t)); }
           }
           return map;
          }
        
          /// <summary>
          /// As the XML type name can be different to our internal class name for that XML type, we need to be able
          /// to expect an XML element name that is different to our internal class name for that XML type.  Hence,
          /// our 'schema' map will contain XmlSerializer's whose keys are based on the XML type name, NOT our
          /// internal class name for that XML type.  This method returns the XML type name given our internal
          /// class we're using to (de)serialize that XML type.  If no XML TypeName is specified in our internal
          /// class's XmlTypeAttribute, we assume an XML type name identical to the internal class name.
          /// </summary>
          /// <param name="t">Our internal class used to (de)serialize an XML type.</param>
          /// <returns>The XML type name corresponding to the given internal class.</returns>
          private string xmlTypeName(Type t) {
           string typeName = t.Name;
           foreach (XmlTypeAttribute ta in t.GetCustomAttributes(typeof(XmlTypeAttribute), true)) {
            if (!string.IsNullOrEmpty(ta.TypeName)) { typeName = ta.TypeName; }
           }
           return typeName;
          }
        
          #endregion
        
          #region IXmlSerializable Members
        
          /// <summary>
          /// Reserved and should not be used.
          /// </summary>
          /// <returns>Must return null.</returns>
          public XmlSchema GetSchema() {
           return null;
          }
        
          /// <summary>
          /// Generates a list of type T objects from their XML representation; stores them in this.Items.
          /// </summary>
          /// <param name="reader">The System.Xml.XmlReader stream from which the objects are deserialized.</param>
          public void ReadXml(XmlReader reader) {
           Map map = loadSchema();
           int depth = reader.Depth;
        
           List<T> items = new List<T>();
           if (!reader.IsEmptyElement && reader.Read()) {
            // While the reader is at a greater depth that the initial depth, ie. at one of the elements
            // inside the list wrapper, the initial depth being that of the list wrapper <ItemList>...
            while (reader.Depth > depth) {
             try { items.Add((T)map[reader.LocalName].Deserialize(reader)); }
             catch (InvalidOperationException iopEx) {
              if (
               iopEx.InnerException != null &&
               iopEx.InnerException.Message.StartsWith("The specified type was not recognized")
              ) { throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because although its element node is a valid type, its attribute-specified type was not recognized.  Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", iopEx); }
             }
             catch (KeyNotFoundException knfEx) {
              throw new InvalidOperationException("Couldn't deserialize node '" + reader.LocalName + "' because its element node was not recognized as a valid type.  Perhaps it needs adding to the ItemList using XmlIncludeAttribute?", knfEx);
             }
             catch (Exception ex) {
              throw ex;
             }
            }
           }
           this.Items = items;
          }
        
          /// <summary>
          /// Converts a list of type T objects into their XML representation; writes them to the specified writer.
          /// </summary>
          /// <param name="writer">The System.Xml.XmlWriter stream to which the objects are serialized.</param>
          public void WriteXml(XmlWriter writer) {
           Map map = loadSchema();
           foreach (T item in this.Items) {
            map[xmlTypeName(item.GetType())].Serialize(writer, item);
           }
          }
        
          #endregion
        
          #region Public properties
        
          public List<T> Items { get; set; }
        
          #endregion
         }
        
         /// <summary>
         /// A regular person.
         /// </summary>
         [XmlType(AnonymousType = false, TypeName = "Person", Namespace = "")]
         [XmlInclude(typeof(PersonBilingual))]
         public class Person {
          public string FullName { get; set; }
          public int Age { get; set; }
          public string Language { get; set; }
         }
        
         /// <summary>
         /// A person who can speak a second language.
         /// </summary>
         [XmlType(AnonymousType = false, TypeName = "PersonBilingual", Namespace = "")]
         public class PersonBilingual : Person {
          public string SecondLanguage { get; set; }
         }
        
         /// <summary>
         /// Some kind of account.
         /// </summary>
         [XmlType(AnonymousType = false, TypeName = "Account", Namespace = "")]
         [XmlInclude(typeof(AccountBank))]
         public class Account {
          public string AcctName { get; set; }
          public string WithCompany { get; set; }
         }
        
         /// <summary>
         /// A bank account.
         /// </summary>
         [XmlType(AnonymousType = false, TypeName = "AccountBank", Namespace = "")]
         public class AccountBank : Account {
          public int BalanceInEuros { get; set; }
         }
        }
        

        感谢大家的帮助!

        【讨论】:

        • 我发现了一种更优雅的设计 ItemList 的方式,就在发布这个之后。请查看我对这个问题的其他答案,以了解更好的方法。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-10
        • 1970-01-01
        • 2021-12-03
        • 1970-01-01
        相关资源
        最近更新 更多