如何使用 XmlSerializer 序列化派生实例?

Posted

技术标签:

【中文标题】如何使用 XmlSerializer 序列化派生实例?【英文标题】:How to use XmlSerializer to serialize derived instances? 【发布时间】:2018-07-03 19:15:41 【问题描述】:

我意识到这看起来与 Using XmlSerializer to serialize derived classes 完全相同,但我不知道如何按照同一问题的指导进行操作:

using System;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace xmlSerializerLab

    public class Utf8StringWriter : System.IO.StringWriter
    
        public override Encoding Encoding => Encoding.UTF8;
    

    [XmlRoot(ElementName = "Query", Namespace = "http://www.opengis.net/wfs")]
    public class Query
    
        [XmlElement(ElementName = "Filter", Namespace = "http://www.opengis.net/ogc")]
        public Filter Filter  get; set; 
    

    [XmlInclude(typeof(PropertyIsOpFilter))]
    [XmlInclude(typeof(PropertyIsEqualToFilter))]
    [XmlInclude(typeof(OpFilterBase))]
    [XmlInclude(typeof(LiteralFilter))]
    [XmlInclude(typeof(Query))]
    [Serializable]
    public class Filter
    
        [XmlElement]
        public Filter And  get; set; 
    

    public class PropertyIsOpFilter : Filter, IXmlSerializable
    

        public Filter LeftOp  get; set; 

        public Filter RightOp  get; set; 

        public XmlSchema GetSchema()
        
            return null;
        

        public void ReadXml(XmlReader reader)  

        public void WriteXml(XmlWriter writer)
        
            Program.ToXml(LeftOp, writer);
            Program.ToXml(RightOp, writer);
        
    

    [XmlRoot("IsEqualTo")]
    public class PropertyIsEqualToFilter : PropertyIsOpFilter  

    public class OpFilterBase : Filter, IXmlSerializable
    
        public string Op  get; set; 
        public object Value  get; set; 

        public XmlSchema GetSchema()
        
            return null;
        

        public void ReadXml(XmlReader reader)  

        public void WriteXml(XmlWriter writer)
        
            if (!String.IsNullOrEmpty(Op))
            
                writer.WriteStartElement(Op);
                writer.WriteValue(Value);
                writer.WriteEndElement();
            
            else
            
                writer.WriteValue(Value);
            
        
    

    public class LiteralFilter : OpFilterBase  


    class Program
    
        public static void ToXml(Object o, XmlWriter writer)
        
            var inputSerializer = new XmlSerializer(o.GetType(), new Type[] 
                typeof(Filter),
                typeof(PropertyIsOpFilter),
                typeof(PropertyIsEqualToFilter),
                typeof(OpFilterBase),
                typeof(LiteralFilter),
                typeof(Query)
            );
            inputSerializer.Serialize(writer, o);
        

        public static string ToXml(Object o)
        
            var inputSerializer = new XmlSerializer(o.GetType());
            using (var writer = new Utf8StringWriter())
            
                using (var xmlWriter = new XmlTextWriter(writer))
                
                    ToXml(o, xmlWriter);
                
                return writer.ToString();
            
        

        static void Main(string[] args)
        
            Filter o = new PropertyIsEqualToFilter()
            
                LeftOp = new LiteralFilter()
                
                    Value = 1
                ,
                RightOp = new LiteralFilter()
                
                    Value = 1
                
            ;

            var query = new Query()
            
                Filter = o
            ;

            Console.WriteLine(ToXml(query));
            Console.ReadLine();
        
    

导致这个异常:

InvalidOperationException:类型 xmlSerializerLab.PropertyIsEqualToFilter 不能用于此 语境。使用 xmlSerializerLab.PropertyIsEqualToFilter 作为 参数、返回类型或类或结构的成员、参数、 返回类型,或成员必须声明为类型 xmlSerializerLab.PropertyIsEqualToFilter(它不能是对象)。 xmlSerializerLab.PropertyIsEqualToFilter 类型的对象可能不是 用于无类型集合,例如 ArrayLists。

据我所知,我需要 PropertyIsOpFilter 和 OpFilterBase 上的 IXmlSerializable,因为我试图以this schema 描述的特定 XML 格式为目标。但我发现我还必须使 Query 类 IXmlSerializable。

这是我希望能够从模型中生成的示例 XML 文档:

<GetFeature xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  service="WFS"
  version="1.1.0"
  maxFeatures="0" xmlns="http://www.opengis.net/wfs">
  <ResultType>Results</ResultType>
  <OutputFormat>text/gml; subtype=gml/3.1.1</OutputFormat>
  <Query
    d2p1:srsName="EPSG:4326" xmlns:d2p1="http://www.opengis.net/ogc">
    <d2p1:Filter>
      <d2p1:IsEqualTo>
        <d2p1:PropertyName>Prop1</d2p1:PropertyName>
        <d2p1:Literal>1</d2p1:Literal>
      </d2p1:IsEqualTo>
    </d2p1:Filter>
  </Query>
</GetFeature>

通过使 Query 类 IXmlSerializable 并编写大量的 WriteXml 和 ReadXml 逻辑,我可以让它工作,但我希望它可以工作而不必做所有这些,因为 XmlRoot 和 XmlAttribute 和 XmlElement 标记应该给出足够的信息给序列化程序,让它知道根据标签名称(匹配 ElementName)实例化哪个类,当然还有如何根据属性进行序列化。

【问题讨论】:

【参考方案1】:

您看到的问题可以通过以下最小示例重现:

public class BaseClass



public class DerivedClass : BaseClass, IXmlSerializable

    #region IXmlSerializable Members

    public XmlSchema GetSchema()  return null; 

    public void ReadXml(XmlReader reader)  throw new NotImplementedException(); 

    public void WriteXml(XmlWriter writer)  

    #endregion

使用序列化代码:

BaseClass baseClass = new DerivedClass();

using (var textWriter = new StringWriter())

    using (var xmlWriter = XmlWriter.Create(textWriter))
    
        var serializer = new XmlSerializer(typeof(BaseClass), new Type[]  typeof(DerivedClass) );
        serializer.Serialize(xmlWriter, baseClass);
    
    Console.WriteLine(textWriter.ToString());

抛出以下异常(示例fiddle #1):

System.InvalidOperationException: There was an error generating the XML document. 
---> System.InvalidOperationException: The type DerivedClass may not be used in this context. To use DerivedClass as a parameter, return type, or member of a class or struct, the parameter, return type, or member must be declared as type DerivedClass (it cannot be object). Objects of type DerivedClass may not be used in un-typed collections, such as ArrayLists.

这是我从XmlSerializer 看到的最无用的异常消息之一。要了解异常,您需要了解XmlSerializer 如何通过[XmlInclude] mechanism 处理多态性。如果我从DerivedClass 中删除IXmlSerializable,则会生成以下XML(fiddle #2):

<BaseClass xsi:type="DerivedClass" />

注意到xsi:type 类型属性了吗?那是XmlSerializer 用来显式断言多态元素类型的w3c standard attribute;它记录在here。当XmlSerializer 反序列化已应用[XmlInclude] 属性的多态类型时(静态或通过您正在使用的constructor),它将查找xsi:type 属性以确定要构造和反序列化的实际类型.

显然,这与IXmlSerializable 冲突。实现此接口的类型应该完全控制其 XML 读取和写入。但是,通过解析和解释xsi:type 属性,XmlSerializer 已经开始自动反序列化,因此由于基类型和派生类型的反序列化策略不一致而引发异常。

更重要的是,将IXmlSerializable 添加到基本类型也不能真正解决问题。如果这样做,则永远不会写入xsi:type 属性,然后在调用ReadXml() 时,基本类型将无条件构造,如fiddle #3所示。

(可以想象,Microsoft 可以实现一个特殊情况,其中XmlSerializer 开始自动反序列化,然后在遇到并构造IXmlSerializable 多态类型时“退后”并将任务交给ReadXml()。但是,他们没有。)

解决方案似乎是使用[XmlInclude] 机制自动序列化您的Filter 类型。事实上,我看不出您需要使用 IXmlSerializable 的任何理由,并且能够通过完全删除 IXmlSerializable 并对命名空间进行一些小改动来成功序列化您的模型:

public static class XmlNamespaces

    public const string OpengisWfs = "http://www.opengis.net/wfs";


[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class Query

    public Filter Filter  get; set; 


[XmlInclude(typeof(PropertyIsOpFilter))]
[XmlInclude(typeof(OpFilterBase))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class Filter

    [XmlElement]
    public Filter And  get; set; 


[XmlInclude(typeof(PropertyIsEqualToFilter))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class PropertyIsOpFilter : Filter

    public Filter LeftOp  get; set; 

    public Filter RightOp  get; set; 


[XmlRoot("IsEqualTo", Namespace = XmlNamespaces.OpengisWfs)]
public class PropertyIsEqualToFilter : PropertyIsOpFilter  

[XmlInclude(typeof(LiteralFilter))]
[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class OpFilterBase : Filter

    public string Op  get; set; 
    public object Value  get; set; 


[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
public class LiteralFilter : OpFilterBase  

注意事项:

为了使[XmlInclude] 机制正常工作,所有包含的类型显然必须与基本类型位于相同的 XML 命名空间中。为了确保这一点,我将[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)] 添加到所有Filter 子类型中。

[XmlInclude(typeof(DerivedType))] 属性可以添加到它们的直接父类型或最低公共基类型。在上面的代码中,我将属性添加到直接父类型,以便中间类型的成员可以成功序列化,例如:

public class SomeClass

    PropertyIsOpFilter  IsOpFilter  get; set; 

考虑将无法实例化的中间类型标记为abstract,例如public abstract class Filter。考虑将“最衍生”的类型标记为sealed,例如public sealed class LiteralFilter

如果使用new XmlSerializer(Type, Type []) 构造函数,则必须静态缓存序列化程序以避免严重的内存泄漏,如here 所述。在我的解决方案中没有必要,但您在问题中使用它。

示例fiddle #4 显示以下 XML 已成功生成:

<Query xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.opengis.net/wfs">
  <Filter xsi:type="PropertyIsEqualToFilter">
    <LeftOp xsi:type="LiteralFilter">
      <Value xsi:type="xsd:int">1</Value>
    </LeftOp>
    <RightOp xsi:type="LiteralFilter">
      <Value xsi:type="xsd:int">1</Value>
    </RightOp>
  </Filter>
</Query>

【讨论】:

很好的答案!我的两个同事提供了相同的解决方案,但没有很好地表达出来!但我不能随意更改 XML 格式。我正在尝试生成与 OGC WFS 兼容的 XML。可以在此处找到更复杂的示例之一:github.com/ca0v/ogc-lab/blob/master/ogc-models/data/wfs/wfs-110/…。我将更新问题以包含这个重要的约束。根据您的分析,XmlInclude 似乎不在讨论范围内。 @CoreyAlix - 也许你需要问第二个问题。 你回答了我的明确问题(是的,可以做到)和我的隐含问题(不,我无法获得我正在寻找的 XML,而无需编写 IXmlSerializable 类)。 @CoreyAlix - 实际上,您可能可以使用XmlElement 而不是XmlInclude。但我们需要查看所需的 XML 而不是所需的类,这就是为什么我建议您再问一个问题。

以上是关于如何使用 XmlSerializer 序列化派生实例?的主要内容,如果未能解决你的问题,请参考以下文章

如何告诉 XmlSerializer 总是用 [DefautValue(...)] 序列化属性?

在C#中 如何序列化图片 用System.Xml.Serialization.XmlSerializer这个可以吗?如果可以的话怎样使用?

如何使 System.Web XmlSerializer 序列化程序编码引号 c#

无法使用 c# xmlserializer 反序列化以前序列化的 XML

XmlSerializer(Type type, Type[] extraTypes) 内存泄漏

使用 XmlSerializer.Deserialize 反序列化时何时调用类构造函数?