【问题标题】:How do I use an XmlSerializer to deserialize an object that might be of a base or derived class without knowing the type beforehand?如何在事先不知道类型的情况下使用 XmlSerializer 反序列化可能属于基类或派生类的对象?
【发布时间】:2011-06-15 15:10:04
【问题描述】:

在 C# 中,如何使用 XmlSerializer 反序列化可能属于基类或多个派生类中的任何一个的对象而不事先知道类型?

我所有的派生类都添加了额外的数据成员。我制作了一个简单的 GUI,可以序列化和反序列化类对象。它将根据用户选择填充的字段将对象序列化为任何合适的继承类(甚至只是基类)。

我对序列化没有任何问题;问题是反序列化。在事先不知道类的情况下,我怎么可能让XmlSerializer 将数据反序列化到正确的派生类?我目前创建了一个 XmlReader 来读取 XML 文件的第一个节点并从中确定类,它似乎适合我的目的,但它似乎是一个极其不雅的解决方案。

我在下面发布了一些示例代码。有什么建议吗?

BaseType objectOfConcern = new BaseType();
XmlSerializer xserializer;
XmlTextReader xtextreader = new XmlTextReader(DEFAULT_FILENAME);

do { xtextreader.Read(); } while (xtextreader.NodeType != XmlNodeType.Element);

string objectType = xtextreader.Name;
xtextreader.Close();

FileStream fstream = new FileStream(DEFAULT_FILENAME, FileMode.Open);

switch (objectType)
    {
case "type1":
    xserializer = new XmlSerializer(typeof(DerivedType));

    objectOfConcern = (DerivedType)xserializer.Deserialize(fstream);

    //Load fields specific to that derived type here
    whatever = (objectOfConcern as DerivedType).NoOfstreamubordinates.ToString();

    case "xxx_1":
        //code here

    case "xxx_2":
        //code here

    case "xxx_n":
        //code here

        //and so forth

    case "BaseType":
    xserializer = new XmlSerializer(typeof(BaseType));
    AssignEventHandler(xserializer);
    objectOfConcern = (BaseType)xserializer.Deserialize(fstream);
}

//Assign all deserialized values from base class common to all derived classes here

//Close the FileStream
fstream.Close();

【问题讨论】:

    标签: c# xml-serialization derived-class xml-deserialization


    【解决方案1】:

    你有一些包含派生类型的根类/标签吗?如果是,您可以使用XmlElementAttribute 将标签名称映射到类型:

    public class RootElementClass
    {
        [XmlElement(ElementName = "Derived1", Type = typeof(Derived1BaseType))]
        [XmlElement(ElementName = "Derived2", Type = typeof(Derived2BaseType))]
        [XmlElement(ElementName = "Derived3", Type = typeof(Derived3BaseType))]
        public BaseType MyProperty { get; set; }
    }
    
    public class BaseType { }
    public class Derived1BaseType : BaseType { }
    public class Derived2BaseType : BaseType { }
    public class Derived3BaseType : BaseType { }
    

    【讨论】:

      【解决方案2】:

      我最近为基类 T 和 T 的任何派生类编写了这个通用序列化器\反序列化器。 到目前为止似乎工作。

      Type[] 数组存储 T 和 T 本身的所有派生类型。 反序列化器会尝试每一个,并在找到正确的时候返回。

      /// <summary>
      /// A generic serializer\deserializer
      /// </summary>
      /// <typeparam name="T"></typeparam>
      public static class Serializer<T>
      {
          /// <summary>
          /// serialize an instance to xml
          /// </summary>
          /// <param name="instance"> instance to serialize </param>
          /// <returns> instance as xml string </returns>
          public static string Serialize(T instance)
          {
              StringBuilder sb = new StringBuilder();
              XmlWriterSettings settings = new XmlWriterSettings();
      
              using (XmlWriter writer = XmlWriter.Create(sb, settings))
              {
                  XmlSerializer serializer = new XmlSerializer(instance.GetType());
                  serializer.Serialize(writer, instance);
              }
      
              return sb.ToString();
          }
      
          /// <summary>
          /// deserialize an xml into an instance
          /// </summary>
          /// <param name="xml"> xml string </param>
          /// <returns> instance </returns>
          public static T Deserialize(string xml)
          {
              using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
              {
                  foreach (Type t in types)
                  {
                      XmlSerializer serializer = new XmlSerializer(t);
                      if (serializer.CanDeserialize(reader))
                          return (T)serializer.Deserialize(reader);
                  }
              }
      
              return default(T);
          }
      
          /// <summary>
          /// store all derived types of T:
          /// is used in deserialization
          /// </summary>
          private static Type[] types = AppDomain.CurrentDomain.GetAssemblies()
                                              .SelectMany(s => s.GetTypes())
                                              .Where(t => typeof(T).IsAssignableFrom(t)
                                                  && t.IsClass
                                                  && !t.IsGenericType)
                                                  .ToArray();
      }
      

      【讨论】:

      • 正是我想要的,谢谢!
      • 太棒了!你拯救了我的一天!
      【解决方案3】:

      如果您未设置使用XmlSerializer,则可以使用DataContractSerializer 和KnownType 属性。

      您需要做的就是为每个子类的父类添加一个KnownType 属性,然后DataContractSerializer 将完成剩下的工作。

      DataContractSerializer 将在序列化为 xml 时添加类型信息,并在反序列化时使用该类型信息来创建正确的类型。

      例如下面的代码:

      [KnownType( typeof( C2 ) )]
      [KnownType( typeof( C3 ) )]
      public class C1 {public string P1 {get;set;}}
      public class C2 :C1 {public string P2 {get;set;}}
      public class C3 :C1 {public string P3 {get;set;}}
      
      class Program
      {
        static void Main(string[] args)
        {
          var c1 = new C1{ P1="c1"};
          var c2 = new C2{ P1="c1", P2="c2"};
          var c3 = new C3{ P1="c1", P3="c3"};
      
          var s = new DataContractSerializer( typeof( C1 ) );
          Test( c1, s );
          Test( c2, s );
          Test( c3, s );
        }
      
        static void Test( C1 objectToSerialize, DataContractSerializer serializer )
        {
          using ( var stream = new MemoryStream() )
          {
            serializer.WriteObject( stream, objectToSerialize );
            stream.WriteTo( Console.OpenStandardOutput() );
            stream.Position = 0;
            var deserialized = serializer.ReadObject( stream );
            Console.WriteLine( Environment.NewLine + "Deserialized Type: " + deserialized.GetType().FullName );              
          }
        }
      }
      

      将输出:

      <C1 xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <P1>c1</P1></C1>
      
      Deserialized Type: ConsoleApplication1.C1
      
      <C1 i:type="C2" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <P1>c1</P1><P2>c2</P2></C1>
      
      Deserialized Type: ConsoleApplication1.C2
      
      <C1 i:type="C3" xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
      <P1>c1</P1><P3>c3</P3></C1>
      
      Deserialized Type: ConsoleApplication1.C3
      

      在输出中,您会注意到 c2 和 c3 的 xml 包含额外的类型信息,这使得 DataContractSerializer.ReadObject 可以创建正确的类型。

      【讨论】:

        【解决方案4】:

        您可以尝试使用构造函数XmlSerializer(Type type, Type[] extraTypes) 来创建适用于所有相关类型的序列化程序。

        【讨论】:

          【解决方案5】:

          你可以使用 XmlInclude

          [XmlInclude(typeof(MyClass))]
          public abstract class MyBaseClass
          {
             //...
          }
          

          否则如果你想在序列化时添加类型:

          Type[] types = new Type[]{ typeof(MyClass) }
          
          XmlSerializer serializer = new XmlSerializer(typeof(MyBaseClass), types);
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-10-21
            • 1970-01-01
            • 2012-11-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-12-13
            相关资源
            最近更新 更多