Сериализация абстрактного класса

Я пытаюсь сериализовать, и я сталкиваюсь с проблемой с классом abstact.

Я googled для ответа, и я нашел это blogitem. Я пробовал это и эту работу.

хорошо, очень хорошо. Но ознакомьтесь с комментарием к пункту:

эта методика как бы прячется истинная проблема, и это неточная реализация проекта ОО узоры, а именно заводской узор.

изменения базы класс ссылка любой новый класс фабрики саморазрушительной.

немного подумав, код может быть изменен на котором любой производной тип может быть связан с абстрактный класс (через чудо интерфейсы) и никакой XmlInclude не будет требуемый.

Я предлагаю дальнейшее исследование моделей фабрика, которая кажется то, что вы пытаетесь реализовать.

о чем говорит комментатор? Он какой-то неопределенный. Может ли кто-нибудь объяснить это более подробно (на примере)? Или он просто несет чушь?

обновление (после прочтения первого ответа)

почему комментатор говорит о

шаблон фабрики

и

код можно изменить на любой производный тип может быть связан с абстрактный класс (по чудо интерфейсы)

?

Он хочет сделать интерфейс, как это?

public interface IWorkaround
{
    void Method();
}

public class SomeBase : IWorkaround
{
    public void Method()
    {
        // some logic here
    }
}

public class SomeConcrete : SomeBase, IWorkaround
{
    public new void Method()
    {
        base.Method();
    }
}

1 ответов


С BinaryFormatter, это не проблема; сериализованный поток содержит метаданные полного типа, поэтому, если у вас есть:

[Serializable] abstract class SomeBase {}
[Serializable] class SomeConcrete : SomeBase {}
...
SomeBase obj = new SomeConcrete();

и сериализовать obj, то он включает в себя "я SomeConcrete" в потоке. Это делает жизнь простой,но многословной, особенно при повторении. Он также хрупкий, поскольку требует одной и той же реализации при десериализации; плохо для разных реализаций клиента/сервера или для долгосрочного хранения.

С XmlSerializer (о котором, я думаю, говорит блог), нет метаданных, но имена элементов (или xsi:type атрибуты) используются, чтобы помочь определить, какой используется. Чтобы это работало, сериализатор необходимо знать заранее какие имена сопоставляются с какими типами.

самый простой способ сделать это-украсить базовый класс подклассами, о которых мы знаем. Затем сериализатор может проверить каждый из них (и любой дополнительные атрибуты xml), чтобы выяснить, что, когда он видит <someConcreteType> элемент, который соответствует SomeConcrete экземпляр (обратите внимание, что имена не должны совпадать, поэтому он не может просто искать его по имени).

[XmlInclude(typeof(SomeConcrete))]
public abstract class SomeBase {}
public class SomeConcrete : SomeBase {}
...
SomeBase obj = new SomeConcrete();
XmlSerializer ser = new XmlSerializer(typeof(SomeBase));
ser.Serialize(Console.Out, obj);

однако, если он эстет (или данные не доступны), то есть альтернатива, вы можете указать все эти данные отдельно через перегруженный конструктор к XmlSerializer. Например, можно найти набор известных подтипов из конфигурация (или, возможно, контейнер IoC) и настройка конструктора вручную. Это не очень сложно, но это достаточно сложно, что это не стоит того, если вы на самом деле это нужно.

public abstract class SomeBase { } // no [XmlInclude]
public class SomeConcrete : SomeBase { }
...
SomeBase obj = new SomeConcrete();
Type[] extras = {typeof(SomeConcrete)}; // from config
XmlSerializer ser = new XmlSerializer(typeof(SomeBase), extras);
ser.Serialize(Console.Out, obj);

кроме того, с XmlSerializer если вы идете по пользовательскому маршруту ctor, важно кэшировать и повторно использовать XmlSerializer экземпляр; в противном случае новая динамическая сборка загружается за использование - очень дорого (их нельзя выгрузить). Если вы используете простой конструктор, он кэширует и повторно использует модель, поэтому используется только одна модель.

YAGNI диктует, что мы должны выбрать самый простой вариант; используя [XmlInclude] устраняет необходимость в сложном конструкторе и устраняет необходимость беспокоиться о кэшировании сериализатора. Другой вариант существует и полностью поддерживается.


Re ваши последующие вопросы:

по "заводской схеме", он говорит о случае, когда ваш код не знаю, о SomeConcrete, возможно, из-за IoC/DI или аналогичных фреймворков; поэтому у вас может быть:

SomeBase obj = MyFactory.Create(typeof(SomeBase), someArgsMaybe);

который вычисляет соответствующий SomeBase конкретная реализация, создает экземпляр и передает его обратно. Очевидно, что если наш код не знает о конкретных типах (потому что они указаны только в файле конфигурации), то мы не можем использовать XmlInclude; но мы можете проанализируйте Данные конфигурации и используйте подход ctor (как указано выше). На самом деле, в большинстве случаев XmlSerializer используется с объектами POCO/ DTO, поэтому это искусственное беспокойство.

и re интерфейсы; то же самое, но более гибкое (интерфейс не требует иерархии типов). Но!--12--> не поддерживает эту модель. Честно говоря, это не его работа. Его задача-позволить вам хранить и транспортировать данные. Не реализация. Любые объекты, созданные xml-схемой, не будут есть методы. Данные конкретны, а не абстрактны. Пока вы думаете "DTO", дебаты по интерфейсу не являются проблемой. Люди, которых раздражает не возможность использования интерфейсов на их границе не охватила разделение проблем, т. е. они пытаются сделать:

Client runtime entities <---transport---> Server runtime entities

а не менее ограничительных

Client runtime entities <---> Client DTO <--- transport--->
           Server DTO <---> Server runtime entities

теперь, во многих (большинство?) случаи ДТО и сущностей can будьте такими же; но если вы пытаетесь сделать что-то, что не нравится транспорту, введите DTO; не боритесь с сериализатором. Та же логика применяется, когда люди изо всех сил пытаются написать свои объект:

class Person {
    public string AddressLine1 {get;set;}
    public string AddressLine2 {get;set;}
}

как xml формы:

<person>
    <address line1="..." line2="..."/>
</person>

если вы хотите этого, intoduce DTO, который соответствует транспорту, и карта между вашей сущностью и DTO:

// (in a different namespace for the DTO stuff)
[XmlType("person"), XmlRoot("person")]
public class Person {
    [XmlElement("address")]
    public Address Address {get;set;}
}
public class Address {
    [XmlAttribute("line1")] public string Line1 {get;set;}
    [XmlAttribute("line2")] public string Line2 {get;set;}
}

это касается и всех других мелочей, таких как:

  • зачем нужен конструктор без параметров?
  • зачем мне нужен сеттер для свойства моей коллекции?
  • почему я не могу использовать неизменяемый тип?
  • почему мой тип быть публичным?
  • как я справляюсь со сложным управлением версиями?
  • как обрабатывать разные клиенты с разными макетами данных?
  • почему я не могу использовать интерфейсы?
  • etc, etc

у вас не всегда есть эти проблемы; но если вы это сделаете-введите DTO (или несколько), и ваши проблемы уйдут. Возвращаясь к вопросу об интерфейсах; типы DTO могут не быть интерфейсными, но ваши типы среды выполнения / бизнеса могут быть.