如何使用 XmlSerializer 序列化派生实例?
How to use XmlSerializer to serialize derived instances?
我意识到这看起来与 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: The type
xmlSerializerLab.PropertyIsEqualToFilter may not be used in this
context. To use xmlSerializerLab.PropertyIsEqualToFilter as a
parameter, return type, or member of a class or struct, the parameter,
return type, or member must be declared as type
xmlSerializerLab.PropertyIsEqualToFilter (it cannot be object).
Objects of type xmlSerializerLab.PropertyIsEqualToFilter may not be
used in un-typed collections, such as ArrayLists.
据我所知,我需要 PropertyIsOpFilter 和 OpFilterBase 上的 IXmlSerializable,因为我正在尝试以 this schema 描述的特定 XML 格式为目标。但我发现我还必须使查询 class 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>
通过使查询 class IXmlSerializable 并编写大量的 WriteXml 和 ReadXml 逻辑,我可以让它工作,但我希望它可以工作而不必执行所有这些操作,因为 XmlRoot 和XmlAttribute 和 XmlElement 标签应该为序列化程序提供足够的信息,以便它根据标签名称(匹配 ElementName)知道要实例化哪个 class 以及如何根据属性进行序列化。
您看到的问题 可以通过以下最小示例重现:
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. If I remove IXmlSerializable
from DerivedClass
, the following XML is generated (fiddle #2):
处理多态性
<BaseClass xsi:type="DerivedClass" />
注意到 xsi:type
类型属性了吗?那是您正在使用的 w3c standard attribute that XmlSerializer
uses to explicitly assert the type of a polymorphic element; it is documented here. When XmlSerializer
is deserializing a polymorphic type to which [XmlInclude]
attributes have been applied (either statically or through the constructor),它将查找 xsi:type
属性以确定要构造和反序列化的实际类型。
显然,这就是与IXmlSerializable
冲突的地方。实现该接口的类型应该完全控制它的XML读写。但是,通过xsi:type
属性的解析和解释,XmlSerializer
已经开始自动反序列化,因此由于base和base的反序列化策略不一致而抛出异常派生类型。
更重要的是,将IXmlSerializable
添加到基本类型也不能真正解决问题如果你这样做,xsi:type
属性永远不会被写入,后来,当ReadXml()
被调用时,将无条件地构造一个基类型的对象,如fiddle #3.
所示
(可以想象,微软可以实施一种特殊情况,其中 XmlSerializer
开始自动反序列化,然后 "backs off" 并在 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
。考虑将 "most derived" 类型标记为 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>
我意识到这看起来与 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: The type xmlSerializerLab.PropertyIsEqualToFilter may not be used in this context. To use xmlSerializerLab.PropertyIsEqualToFilter as a parameter, return type, or member of a class or struct, the parameter, return type, or member must be declared as type xmlSerializerLab.PropertyIsEqualToFilter (it cannot be object). Objects of type xmlSerializerLab.PropertyIsEqualToFilter may not be used in un-typed collections, such as ArrayLists.
据我所知,我需要 PropertyIsOpFilter 和 OpFilterBase 上的 IXmlSerializable,因为我正在尝试以 this schema 描述的特定 XML 格式为目标。但我发现我还必须使查询 class 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>
通过使查询 class IXmlSerializable 并编写大量的 WriteXml 和 ReadXml 逻辑,我可以让它工作,但我希望它可以工作而不必执行所有这些操作,因为 XmlRoot 和XmlAttribute 和 XmlElement 标签应该为序列化程序提供足够的信息,以便它根据标签名称(匹配 ElementName)知道要实例化哪个 class 以及如何根据属性进行序列化。
您看到的问题 可以通过以下最小示例重现:
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. If I remove IXmlSerializable
from DerivedClass
, the following XML is generated (fiddle #2):
<BaseClass xsi:type="DerivedClass" />
注意到 xsi:type
类型属性了吗?那是您正在使用的 w3c standard attribute that XmlSerializer
uses to explicitly assert the type of a polymorphic element; it is documented here. When XmlSerializer
is deserializing a polymorphic type to which [XmlInclude]
attributes have been applied (either statically or through the constructor),它将查找 xsi:type
属性以确定要构造和反序列化的实际类型。
显然,这就是与IXmlSerializable
冲突的地方。实现该接口的类型应该完全控制它的XML读写。但是,通过xsi:type
属性的解析和解释,XmlSerializer
已经开始自动反序列化,因此由于base和base的反序列化策略不一致而抛出异常派生类型。
更重要的是,将IXmlSerializable
添加到基本类型也不能真正解决问题如果你这样做,xsi:type
属性永远不会被写入,后来,当ReadXml()
被调用时,将无条件地构造一个基类型的对象,如fiddle #3.
(可以想象,微软可以实施一种特殊情况,其中 XmlSerializer
开始自动反序列化,然后 "backs off" 并在 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
。考虑将 "most derived" 类型标记为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>