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

时间:2018-01-24 16:47:10

标签: c# xmlserializer derived-class

我意识到这看起来与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 个答案:

答案 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 attributeXmlSerializer用于显式断言多态元素的类型;记录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 -> ...)