如何使用 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