【问题标题】:Polymorphic Types and IXmlSerializable多态类型和 IXmlSerializable
【发布时间】:2011-05-14 07:21:23
【问题描述】:

.NET 中的 XML 序列化允许通过 XmlSerializer 构造函数的 extraTypes[] 参数实现多态对象。它还允许为实现 IXmlSerializable 的类型自定义 XML 序列化。

但是,我无法将这两个功能结合起来——如这个最小的示例所示:

using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace CsFoo
{
    public class CustomSerializable : IXmlSerializable
    {
        public XmlSchema GetSchema() { return null; }
        public void ReadXml(XmlReader xr) { }
        public void WriteXml(XmlWriter xw) { }
    }

    class CsFoo
    {
        static void Main()
        {
            XmlSerializer xs = new XmlSerializer(
                typeof(object),
                new Type[] { typeof(CustomSerializable) });

        xs.Serialize(new StringWriter(), new CustomSerializable());
    }
}

最后一行抛出 System.InvalidOperationException 并带有此消息:

CsFoo.CustomSerializable 类型不能在此上下文中用于 使用 CsFoo.CustomSerializable 作为参数,返回 类型,或类或结构的成员,参数,返回 类型或成员必须声明为类型 CsFoo.CustomSerializable (它不能是对象)。 CsFoo.CustomSerializable 类型的对象 不能用于无类型集合,例如 ArrayLists。

遍历动态生成的 XML 程序集,我们最终通过调用返回到 .NET 标准库代码:

System.Xml.Serialization.XmlSerializationWriter.WriteTypedPrimitive(
     String, String, Object, Boolean) : Void

反过来,这会导致:

protected Exception CreateUnknownTypeException(Type type)
{
    if (typeof(IXmlSerializable).IsAssignableFrom(type))
    {
        return new InvalidOperationException(
            Res.GetString("XmlInvalidSerializable",
            new object[] { type.FullName }));
    }

    // Rest omitted...

Reflector 显示 XmlInvalidSerializable 资源与上面的字符串相对应 - 即,WriteTypedPrimitive 不喜欢 IXmlSerializable

如果我们生成一个非多态序列化器,像这样:

XmlSerializer xs = new XmlSerializer(typeof(CustomSerializable));

.NET 将生成一个调用:

System.Xml.Serialization.XmlSerializationWriter.WriteSerializable(
    IXmlSerializable, String, String, Boolean) : Void 

这会正确处理IXmlSerializable。有人知道为什么 .NET 在多态情况下不使用这个函数吗?查看 XML 序列化程序生成的 C#,在我看来这可以很容易地完成。这是我从 XML 序列化程序中获得的一些代码,其中包含未经测试的解决方案:

void Write1_Object(string n, string ns, global::System.Object o, 
   bool isNullable, bool needType)
{
    if ((object)o == null)
    {
        if (isNullable) WriteNullTagLiteral(n, ns);
        return;
    }
    if (!needType)
    {
        System.Type t = o.GetType();
        if (t == typeof(global::System.Object))
        {
        }
>>> patch begin <<<
+         else if (typeof(IXmlSerializable).IsAssignableFrom(t))
+         {
+             WriteSerializable((System.Xml.Serialization.IXmlSerializable)
                   ((global::CsFoo.CustomSerializable)o), 
+                  @"CustomSerializable", @"", true, true);
+         }
>>> patch end <<<
        else
        {
            WriteTypedPrimitive(n, ns, o, true);
            return;
        }
    }
    WriteStartElement(n, ns, o, false, null);
    WriteEndElement(o);
}

这是出于技术原因还是仅是功能限制?不支持的功能,还是我的白痴?我的 intertubes Google 技能让我失望。

我确实在这里找到了一些相关问题,其中“C# Xml-Serializing a derived class using IXmlSerializable”最为相关。这让我相信这根本不可能。

在这种情况下,我目前的想法是在根基类中注入一个默认的IXmlSerializable 实现。然后一切都将是IXmlSerializable,并且.NET 不会抱怨。我可以使用 Reflection.Emit 为每种具体类型生成 ReadXmlWriteXml 主体,生成的 XML 看起来与使用库时相同。

有些人在遇到 XML 序列化问题时会想“我知道,我会使用 Reflection.Emit 来生成代码”。现在他们有两个问题。


附:笔记;我知道 .NET 的 XML 序列化的替代方案,并且知道它有局限性。我也知道保存 POCO 比处理抽象数据类型要简单得多。但是我有一堆遗留代码,需要对现有 XML 模式的支持。

因此,虽然我很欣赏在SomeOtherXMLYAMLXAMLProtocolBuffersDataContractRandomJsonLibraryThrift 或您的 MorseCodeBasedSerializeToMp3 库中显示这很容易的回复 -嘿,我可能会学到一些东西——我希望的是一个 XML 序列化程序的解决方法,如果不是解决方案的话。

【问题讨论】:

  • 尽管添加的时间很晚,但无论如何:在 IXmlSerializable 的情况下,具有额外类型的解决方案对我不起作用。然而this approach 正在工作,因为它允许反序列化的类与属性类 (!) 不同,因此任何找到要创建的正确类型的自定义逻辑都可以通过这种方式实现。
  • 还有另一个解决方案声明为here(使用XmlSchemaProvider 属性将xml 类型绑定到.net 类型),但它缺少代码,我无法实现这个想法。有人吗?

标签: c# xml-serialization


【解决方案1】:

使用object 时,我能够重现您的问题:

XmlSerializer xs = new XmlSerializer(
    typeof(object),
    new Type[] { typeof(CustomSerializable) });

但是,我随后创建了CustomSerializable 的派生类:

public class CustomSerializableDerived : CustomSerializable
{
}

并尝试对其进行序列化:

XmlSerializer xs = new XmlSerializer(
    typeof(CustomSerializable),
    new Type[] { typeof(CustomSerializableDerived) });

这行得通。

因此,问题似乎仅限于您将“object”指定为要序列化的类型的情况,但如果您指定了具体的基本类型,则不会。

我会在早上做更多的研究。

【讨论】:

    【解决方案2】:

    首先发帖声明

    .NET 中的 XML 序列化允许通过 XmlSerializer 构造函数的 extraTypes[] 参数实现多态对象。

    具有误导性。根据MSDN,extratypes 用于:

    如果 propertyfield 返回 array,则 extraTypes 参数指定可以插入到数组中的对象。

    意思是如果你的序列化对象图中的某处多态对象通过数组返回,它们可以被处理。

    虽然我还没有真正找到如何将多态类型序列化为根 XML 对象的解决方案,但我能够使用标准 XML 序列化程序或 IXmlSerializable 序列化对象图中的多态类型。我的解决方案见下文:

    using System.IO;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;
    
    namespace CsFoo
    {
      public class Foo
      {
        [XmlElement("BarStandard", typeof(BarStandardSerializable))]
        [XmlElement("BarCustom", typeof(BarCustomSerializable))]
        public Bar BarProperty { get; set; }
      }
    
      public abstract class Bar
      { }
    
      public class BarStandardSerializable : Bar
      { }
    
      public class BarCustomSerializable : Bar, IXmlSerializable
      {
        public XmlSchema GetSchema() { return null; }
        public void ReadXml(XmlReader xr) { }
        public void WriteXml(XmlWriter xw) { }
      }
    
      class CsFoo
      {
        static void Main()
        {
            StringWriter sw = new StringWriter();
            Foo f1 = new Foo() { BarProperty = new BarCustomSerializable() };
            XmlSerializer xs = new XmlSerializer(typeof(Foo));
    
            xs.Serialize(sw, f1);
            StringReader sr= new StringReader(sw.ToString());
            Foo f2 = (Foo)xs.Deserialize(sr);
        }
      }
    }
    

    请注意,使用任一

    XmlSerializer xs = new XmlSerializer(typeof(Foo), 
                new Type[] { typeof(BarStandardSerializable),
                typeof(BarCustomSerializable)});
    

    [XmlInclude(typeof(BarCustomSerializable))]
    [XmlInclude(typeof(BarStandardSerializable))]
    public abstract class Bar
    { }
    

    如果没有定义 XmlElement,会导致代码在序列化时失败。

    【讨论】:

    • 未来读者请注意:如果BarProperty 类似于数组,则必须使用[XmlArrayItem(/* ... */)] 而不是[XmlElement(/* ... */)]
    【解决方案3】:

    为了让 IxmlSerializable 工作,该类需要有一个 null 构造函数。

    考虑一个基类

    • MyBaseXmlClass:IXmlSerializable
      -- 实现 GetSchema
    • MyXmlClass:MyBaseXmlClass
      -- ReadXml
      • WriteXml

    【讨论】:

    • -1:他确实有一个默认构造函数;您可以使用实际代码而不仅仅是文本来说明您的观点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-01
    相关资源
    最近更新 更多