【问题标题】:Generic List XML Serialization with different classes具有不同类的通用列表 XML 序列化
【发布时间】:2012-12-10 05:36:55
【问题描述】:

我有以下代码:

BaseContent.cs

public class BaseContent
{
   // Some auto properties
}

News.cs

public class News : BaseContent
{
   // Some more auto properties
}

Events.cs

public class Event : BaseContent
{
   // Some more auto properites
}

GenericResponse.cs

public class GenericResponse<T> 
{
  [XmlArray("Content")]
  [XmlArrayItem("NewsObject", typeof(News)]
  [XmlArrayItem("EventObject", typeof(Event)]
  public List<T> ContentItems { get; set; }
}

NewsResponse.cs

public class NewsResponse : GenericResponse<News> {}

EventResponse.cs

public class EventResponse : GenericResponse<Event> {}

如您所见,我有一个基类 BaseContent 和两个派生自它的类。接下来我有一个通用响应类,因为 xml 文件的结构始终相同,但它们在某些属性上有所不同。

我想我可以用[XmlArrayItem] 指定用于特定类的名称。但现在我得到了错误:

System.InvalidOperationException:无法生成临时类(结果=1)。 错误 CS0012:类型“System.Object”在未引用的程序集中定义。您必须添加对程序集“System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”的引用。

我无法添加此引用,因为我正在使用 Windows 8 应用程序。

如果我注释掉 [XmlArrayItem] 之一,它运行良好。

有人有解决这个问题的想法吗?

更新 我可以使用 DataContractSerializer,因为我必须使用 XmlAttributes

【问题讨论】:

    标签: c# generics xml-serialization microsoft-metro .net-4.5


    【解决方案1】:

    考虑使用 DataContractSerializer 而不是 XmlSerializer。

    XmlSerializer 在运行时生成一个临时程序集,以加快速度 序列化 - 稍后的反序列化过程。因此它需要代码编译。

    另一方面,DataContractSerializer 没有。如果您使用 DataContractSerializer 方法您必须使用适当的属性,以便您控制“继承” 问题。

    【讨论】:

    • 抱歉,DataContractSerializer 是不可能的。我需要有 XmlAttributes
    【解决方案2】:

    我找到了我的解决方案。不是最好的,但它正在工作。

    我实现了 IXmlSerializable 并自己处理了这些东西:

    public void ReadXml(System.Xml.XmlReader reader)
    {
        reader.Read();
        reader.MoveToContent();
    
        if (reader.LocalName == "AnotherNode")
        {
            var innerXml = Serializer<AnotherClass>.CreateSerializer();
            Remove = (AnotherClass) innerXml.Deserialize(reader);
            reader.MoveToContent();
        }
    
        reader.Read();
    
        // Here is the trick
        if (reader.IsStartElement())
        {
            do
            {
                var innerXml = Serializer<T>.CreateSerializer();
    
                var obj = (T) innerXml.Deserialize(reader);
                Updates.Add(obj);
            } while (reader.MoveToContent() == XmlNodeType.Element);
        }
    }
    
    public void WriteXml(System.Xml.XmlWriter writer)
    {
        var removeSerializer = Serializer<RemoveElement>.CreateSerializer();
        removeSerializer.Serialize(writer, Remove);
    
        if (Updates.Any())
        {
            var innerXml = Serializer<T>.CreateSerializer();
            writer.WriteStartElement("ContentUpdates");
            foreach (var update in Updates)
            {
                innerXml.Serialize(writer, update);
            }
            writer.WriteEndElement();
        }
    }
    

    【讨论】:

      【解决方案3】:

      编辑:请随意下载demo project

      您没有提供对象的所有属性,所以请允许我添加一些 - 仅作为示例:

      public class BaseContent
      {
          [XmlAttribute("Name")]
          public string Name { get; set; }
      }
      
      [XmlType(TypeName = "EventObject")]
      public class Event : BaseContent
      {
          [XmlAttribute("EventId")]
          public int EventId { get; set; }
      }
      
      [XmlType(TypeName = "NewsObject")]
      public class News : BaseContent
      {
          [XmlAttribute("NewsId")]
          public int NewsId { get; set; }
      }
      

      GenericResponse.cs 可以这样定义 - 无需为数组项指定 typeof:

      public class GenericResponse<T>
      {
          [XmlArray("Content")]
          public List<T> ContentItems { get; set; }
      
          public GenericResponse()
          {
              this.ContentItems = new List<T>();
          }
      }
      

      然后你有响应类:

      public class EventResponse : GenericResponse<Event>
      {
      }
      
      public class NewsResponse : GenericResponse<News>
      {
      }
      

      示例 1:序列化 EventResponse 对象

      var response = new EventResponse
      {
          ContentItems = new List<Event> 
          {
              new Event {
                  EventId = 1,
                  Name = "Event 1"
              },
      
              new Event {
                  EventId = 2,
                  Name = "Event 2"
              }
          }
      };
      
      string xml = XmlSerializer<EventResponse>.Serialize(response);
      

      输出 XML:

      <?xml version="1.0" encoding="utf-8"?>
      <EventResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
          <Content>
              <EventObject Name="Event 1" EventId="1" />
              <EventObject Name="Event 2" EventId="2" />
          </Content>
      </EventResponse>
      

      如果您尝试对 NewsResponse 进行相同操作,它会正常工作。顺便说一句,我正在使用我的generic XmlSerializer,点击链接了解更多信息。

      XmlSerializer.cs:

      /// <summary>
      /// XML serializer helper class. Serializes and deserializes objects from/to XML
      /// </summary>
      /// <typeparam name="T">The type of the object to serialize/deserialize.
      /// Must have a parameterless constructor and implement <see cref="Serializable"/></typeparam>
      public class XmlSerializer<T> where T: class, new()
      {
          /// <summary>
          /// Deserializes a XML string into an object
          /// Default encoding: <c>UTF8</c>
          /// </summary>
          /// <param name="xml">The XML string to deserialize</param>
          /// <returns>An object of type <c>T</c></returns>
          public static T Deserialize(string xml)
          {
              return Deserialize(xml, Encoding.UTF8, null);
          }
      
          /// <summary>
          /// Deserializes a XML string into an object
          /// Default encoding: <c>UTF8</c>
          /// </summary>
          /// <param name="xml">The XML string to deserialize</param>
          /// <param name="encoding">The encoding</param>
          /// <returns>An object of type <c>T</c></returns>
          public static T Deserialize(string xml, Encoding encoding)
          {
              return Deserialize(xml, encoding, null);
          }
      
          /// <summary>
          /// Deserializes a XML string into an object
          /// </summary>
          /// <param name="xml">The XML string to deserialize</param>
          /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlReaderSettings"/></param>
          /// <returns>An object of type <c>T</c></returns>
          public static T Deserialize(string xml, XmlReaderSettings settings)
          {
              return Deserialize(xml, Encoding.UTF8, settings);
          }
      
          /// <summary>
          /// Deserializes a XML string into an object
          /// </summary>
          /// <param name="xml">The XML string to deserialize</param>
          /// <param name="encoding">The encoding</param>
          /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlReaderSettings"/></param>
          /// <returns>An object of type <c>T</c></returns>
          public static T Deserialize(string xml, Encoding encoding, XmlReaderSettings settings)
          {
              if (string.IsNullOrEmpty(xml))
                  throw new ArgumentException("XML cannot be null or empty", "xml");
      
              XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
      
              using (MemoryStream memoryStream = new MemoryStream(encoding.GetBytes(xml)))
              {
                  using (XmlReader xmlReader = XmlReader.Create(memoryStream, settings))
                  {
                      return (T) xmlSerializer.Deserialize(xmlReader);
                  }
              }
          }
      
          /// <summary>
          /// Deserializes a XML file.
          /// </summary>
          /// <param name="filename">The filename of the XML file to deserialize</param>
          /// <returns>An object of type <c>T</c></returns>
          public static T DeserializeFromFile(string filename)
          {
              return DeserializeFromFile(filename, new XmlReaderSettings());
          }
      
          /// <summary>
          /// Deserializes a XML file.
          /// </summary>
          /// <param name="filename">The filename of the XML file to deserialize</param>
          /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlReaderSettings"/></param>
          /// <returns>An object of type <c>T</c></returns>
          public static T DeserializeFromFile(string filename, XmlReaderSettings settings)
          {
              if (string.IsNullOrEmpty(filename))
                  throw new ArgumentException("filename", "XML filename cannot be null or empty");
      
              if (! File.Exists(filename))
                  throw new FileNotFoundException("Cannot find XML file to deserialize", filename);
      
              // Create the stream writer with the specified encoding
              using (XmlReader reader = XmlReader.Create(filename, settings))
              {
                  System.Xml.Serialization.XmlSerializer xmlSerializer = new System.Xml.Serialization.XmlSerializer(typeof(T));
                  return (T) xmlSerializer.Deserialize(reader);
              }
          }
      
          /// <summary>
          /// Serialize an object
          /// </summary>
          /// <param name="source">The object to serialize</param>
          /// <returns>A XML string that represents the object to be serialized</returns>
          public static string Serialize(T source)
          {
              // indented XML by default
              return Serialize(source, null, GetIndentedSettings());
          }
      
          /// <summary>
          /// Serialize an object
          /// </summary>
          /// <param name="source">The object to serialize</param>
          /// <param name="namespaces">Namespaces to include in serialization</param>
          /// <returns>A XML string that represents the object to be serialized</returns>
          public static string Serialize(T source, XmlSerializerNamespaces namespaces)
          {
              // indented XML by default
              return Serialize(source, namespaces, GetIndentedSettings());
          }
      
          /// <summary>
          /// Serialize an object
          /// </summary>
          /// <param name="source">The object to serialize</param>
          /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param>
          /// <returns>A XML string that represents the object to be serialized</returns>
          public static string Serialize(T source, XmlWriterSettings settings)
          {
              return Serialize(source, null, settings);
          }
      
          /// <summary>
          /// Serialize an object
          /// </summary>
          /// <param name="source">The object to serialize</param>
          /// <param name="namespaces">Namespaces to include in serialization</param>
          /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param>
          /// <returns>A XML string that represents the object to be serialized</returns>
          public static string Serialize(T source, XmlSerializerNamespaces namespaces, XmlWriterSettings settings)
          {
              if (source == null)
                  throw new ArgumentNullException("source", "Object to serialize cannot be null");
      
              string xml = null;
              XmlSerializer serializer = new XmlSerializer(source.GetType());
      
              using (MemoryStream memoryStream = new MemoryStream())
              {
                  using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, settings))
                  {
                      System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(T));
                      x.Serialize(xmlWriter, source, namespaces);
      
                      memoryStream.Position = 0; // rewind the stream before reading back.
                      using (StreamReader sr = new StreamReader(memoryStream))
                      {
                          xml = sr.ReadToEnd();
                      } 
                  }
              }
      
              return xml;
          }
      
          /// <summary>
          /// Serialize an object to a XML file
          /// </summary>
          /// <param name="source">The object to serialize</param>
          /// <param name="filename">The file to generate</param>
          public static void SerializeToFile(T source, string filename)
          {
              // indented XML by default
              SerializeToFile(source, filename, null, GetIndentedSettings());
          }
      
          /// <summary>
          /// Serialize an object to a XML file
          /// </summary>
          /// <param name="source">The object to serialize</param>
          /// <param name="filename">The file to generate</param>
          /// <param name="namespaces">Namespaces to include in serialization</param>
          public static void SerializeToFile(T source, string filename, XmlSerializerNamespaces namespaces)
          {
              // indented XML by default
              SerializeToFile(source, filename, namespaces, GetIndentedSettings());
          }
      
          /// <summary>
          /// Serialize an object to a XML file
          /// </summary>
          /// <param name="source">The object to serialize</param>
          /// <param name="filename">The file to generate</param>
          /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param>
          public static void SerializeToFile(T source, string filename, XmlWriterSettings settings)
          {
               SerializeToFile(source, filename, null, settings);
          }
      
          /// <summary>
          /// Serialize an object to a XML file
          /// </summary>
          /// <param name="source">The object to serialize</param>
          /// <param name="filename">The file to generate</param>
          /// <param name="namespaces">Namespaces to include in serialization</param>
          /// <param name="settings">XML serialization settings. <see cref="System.Xml.XmlWriterSettings"/></param>
          public static void SerializeToFile(T source, string filename, XmlSerializerNamespaces namespaces, XmlWriterSettings settings)
          {
              if (source == null)
                  throw new ArgumentNullException("source", "Object to serialize cannot be null");
      
              XmlSerializer serializer = new XmlSerializer(source.GetType());
      
              using (XmlWriter xmlWriter = XmlWriter.Create(filename, settings))
              {
                  System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(typeof(T));
                  x.Serialize(xmlWriter, source, namespaces);
              }
          }
      
          #region Private methods
      
      
          private static XmlWriterSettings GetIndentedSettings()
          {
              XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
              xmlWriterSettings.Indent = true;
              xmlWriterSettings.IndentChars = "\t";
      
              return xmlWriterSettings;
          }
      
          #endregion
      }
      

      【讨论】:

      • 非常好的答案,但目前不完全是我需要的。在您的情况下,XML 将是 等等。这对我不起作用。如果是事件,我需要 EventObject,如果是新闻,我需要 NewsObject。我试图通过将 [XmlType("EventObject")] 添加到您的事件类来做到这一点,但也没有帮助。好吧,对我来说最简单的事情是将我的类“事件”重命名为“EventObject”并删除 [XmlArrayItem]-Attribute。但我不喜欢将对象命名为“*Object”。
      • @Raubi:这只是一个示例,您可以按照自己的方式自定义 XML。我不能给你一个更好的答案,因为我不知道你想要什么 XML 输出。请编辑您的答案并为您的课程添加预期的 XML 输出。
      • @Raubi:编辑了我的答案。我不确定 XML 是否正是您所需要的,但我认为您会在我的回答中找到自定义 XML 所需的一切。随意下载演示项目,链接在顶部。 PS:新年快乐!
      • 现在它正是我需要的 :-) 我以为我试过这个,但也许我没有。它就像一个魅力。 :-)
      【解决方案4】:

      听起来 就像是针对 Windows Store 平台的 XmlSerializer 的代码发射器中的一个错误,但它有点没有实际意义,因为它在常规 .NET 上仍然失败,但有一个不同的信息:

      无法生成临时类(结果=1)。

      错误 CS0030:无法将类型“新闻”转换为“事件”

      错误 CS1502:'System.Collections.Generic.List.Add(News)' 的最佳重载方法匹配有一些无效参数

      错误 CS1503:参数 1:无法从“事件”转换为“新闻”

      所以基本上,它看起来像是一个不受支持的场景,在两种场景中表现不同。从根本上说,问题在于您的注释在说(对于T=News)“如果此NewsNews,则调用元素'NewsObject';如果此News 是@987654327 @,调用元素 'EventObject'" - 这很奇怪,因为 News 从不 Event 等等。

      不过,好消息是您想要做的事情只需使用[XmlType(...)]

      [XmlType("NewsObject")]
      public class News : BaseContent
      {
          // Some more auto properties
          public int B { get; set; }
      }
      [XmlType("EventObject")]
      public class Event : BaseContent
      {
          // Some more auto properites
          public int C { get; set; }
      }
      ...
      public class GenericResponse<T>
      {
          [XmlArray("Content")]
          public List<T> ContentItems { get; set; }
      }
      

      注意:上面没有[XmlArrayItem(...)]

      然后输出(在格式化、清理等之后):

      <NewsResponse>
          <Content>
              <NewsObject><A>1</A><B>2</B></NewsObject>
          </Content>
      </NewsResponse>
      

      通过测试代码:

      var obj = new NewsResponse { ContentItems = new List<News> {
          new News { A = 1, B = 2 } } };
      
      var sw = new StringWriter();
      using (var xw = System.Xml.XmlWriter.Create(sw))
      {
          var ser = new XmlSerializer(obj.GetType());
          ser.Serialize(xw, obj);
      }
      string xml = sw.ToString();
      

      如果你需要更多的控制,那么XmlAttributeOverrides 是值得关注的;但是您必须缓存使用 XmlAttributeOverrides 创建的序列化程序,以避免程序集出血和内存泄漏。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-04-13
        • 1970-01-01
        • 1970-01-01
        • 2014-03-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多