我意识到这看起来与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)知道要实例化的类,当然还有基于属性的序列化。
答案 0 :(得分:2)
您看到的问题可以使用以下最小示例进行复制:
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处理多态性。如果我从IXmlSerializable
删除DerivedClass
,则会生成以下XML(fiddle #2):
<BaseClass xsi:type="DerivedClass" />
请注意xsi:type
类型属性?这是w3c standard attribute,XmlSerializer
用于显式断言多态元素的类型;记录here。当XmlSerializer
反序列化已应用[XmlInclude]
属性的多态类型时(静态或通过您正在使用的constructor),它会查找xsi:type
属性确定要构造和反序列化的实际类型。
显然,这与IXmlSerializable
发生冲突。实现此接口的类型应完全控制其XML读写。但是,通过解析和解释xsi:type
属性,XmlSerializer
已经开始自动反序列化,因此由于基类和派生类型的反序列化策略不一致而抛出异常。
更重要的是,将IXmlSerializable
添加到基本类型并不能真正解决问题如果这样做,xsi:type
属性永远不会被写入,之后,当ReadXml()
是如果被调用,基类型的对象将被无条件地构造,如fiddle #3。
(可以想象,Microsoft可以实现XmlSerializer
开始自动反序列化的特殊情况,然后“退避”并在ReadXml()
多态类型为IXmlSerializable
时将任务交给Filter
但是,他们没有。)
解决方案似乎是使用[XmlInclude]
机制自动序列化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]
注意:
要使[XmlRoot(Namespace = XmlNamespaces.OpengisWfs)]
机制起作用,所有包含的类型显然必须与基本类型位于同一XML命名空间中。为确保这一点,我向所有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 [])
如果使用<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>
构造函数,则必须静态缓存序列化程序以避免严重的内存泄漏,如here所述。在我的解决方案中没有必要,但你在问题中使用它。
示例fiddle #4显示已成功生成以下XML:
myList.parallelStream().map(e -> ...)