Полиморфные типы и IXmlSerializable - PullRequest
9 голосов
/ 10 марта 2009

Сериализация XML в .NET допускает полиморфные объекты через параметр extraTypes[] конструктора XmlSerializer. Это также позволяет настраивать сериализацию XML для типов, которые реализуют IXmlSerializable.

Однако я не могу объединить эти две функции - как показано в этом минимальном примере:

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 не использует эту функцию в полиморфном случае? Глядя на C #, который генерирует сериализатор XML, мне кажется, что это можно сделать довольно легко. Вот некоторый код, полученный от сериализатора 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);
}

Это пропущено по техническим причинам или просто из-за ограничений функции? Неподдерживаемая функция или мой идиотизм? Мои навыки Google не помогают мне.

Здесь я нашел несколько связанных вопросов, причем " C # Xml-Сериализация производного класса с использованием IXmlSerializable " является наиболее актуальной. Это заставляет меня поверить, что это просто невозможно.

В этом случае моя текущая мысль - внедрить реализацию IXmlSerializable по умолчанию в корневой базовый класс. Тогда все будет IXmlSerializable, и .NET не будет жаловаться. Я могу использовать Reflection.Emit для извлечения тел ReadXml и WriteXml для каждого конкретного типа, генерируя XML, который выглядел бы так же, как если бы я использовал библиотеку один.

Некоторые люди, сталкиваясь с проблемой сериализации XML, думают: «Я знаю, я буду использовать Reflection.Emit для генерации кода». Теперь у них две проблемы.


P.S. Заметка; Я знаю об альтернативах сериализации .NET XML и знаю, что у нее есть ограничения. Я также знаю, что сохранить POCO намного проще, чем иметь дело с абстрактными типами данных. Но у меня есть куча устаревшего кода, и мне нужна поддержка существующих схем XML.

Так что, хотя я ценю ответы, которые показывают, как легко это сделать в SomeOtherXML, YAML, XAML, ProtocolBuffers, DataContract, RandomJsonLibrary, Thrift или вашей библиотеке MorseCodeBasedSerializeToMp3 - эй, я мог бы кое-что выучить - то, на что я надеюсь, это обходной путь сериализатора XML, если не решение.

Ответы [ 3 ]

2 голосов
/ 28 июля 2009

Я смог воспроизвести вашу проблему при использовании 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) });

Это сработало.

Таким образом, похоже, что проблема ограничена случаем, когда вы указываете «объект» как тип для сериализации, но не если вы указываете конкретный базовый тип.

Я проведу еще несколько исследований утром.

1 голос
/ 14 мая 2011

Постеры в первую очередь

Сериализация XML в .NET допускает полиморфные объекты через параметр extraTypes [] конструктора XmlSerializer.

вводит в заблуждение. Согласно MSDN экстратипы используются для:

Если свойство или возвращает поле массив , параметр 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 приведет к сбою кода при сериализации.

0 голосов
/ 29 июля 2009

Для работы IxmlSerializable у класса должен быть нулевой конструктор.

Рассмотрим базовый класс

  • MyBaseXmlClass: IXmlSerializable
    - реализовать GetSchema
  • MyXmlClass: MyBaseXmlClass
    - ReadXml
    • WriteXml
...