【问题标题】:How to use XmlSerializer to serialize derived instances?如何使用 XmlSerializer 序列化派生实例?
【发布时间】: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)实例化哪个类,当然还有如何根据属性进行序列化。

【问题讨论】:

    标签: c# xmlserializer derived-class


    【解决方案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 而不是所需的类,这就是为什么我建议您再问一个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-05
    相关资源
    最近更新 更多