多态类型和IXmlSerializable

时间:2009-03-10 19:18:05

标签: c# xml-serialization

.NET中的XML序列化允许多态对象通过extraTypes[]构造函数的XmlSerializer参数。它还允许为实现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...

反射器显示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谷歌技能让我失望。

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

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

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


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

因此,虽然我感谢回复显示SomeOtherXMLYAMLXAMLProtocolBuffersDataContractRandomJsonLibraryThrift,或者你的MorseCodeBasedSerializeToMp3库 - 嘿,我可能会学到一些东西 - 我希望的是一个XML序列化器解决方案,如果不是解决方案的话。

3 个答案:

答案 0 :(得分:2)

使用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”指定为序列化类型的情况,但如果指定具体的基本类型则不会。

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

答案 1 :(得分:1)

首先是海报声明

  

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

具有误导性。根据{{​​3}},extratypes用于:

  

如果属性字段返回 数组,则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,会导致代码在序列化时失败。

答案 2 :(得分:0)

为了使IxmlSerializable起作用,该类需要有一个null构造函数。

考虑一个基类

  • MyBaseXmlClass:IXmlSerializable的
    - 实施GetSchema
  • MyXmlClass:MyBaseXmlClass
    - ReadXml
    • 中WriteXML