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

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

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

Хорошо, очень приятно. Но посмотрите комментарий к элементу:

Эта методология, кажется, скрывается истинная проблема, и это неточная реализация OO-дизайна шаблоны, а именно шаблон factory.

Необходимо изменить базовый класс на ссылка на любой новый класс factoryпагубный.

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

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

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

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

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

factory шаблон

и

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

?

Он хочет создать интерфейс, например?

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] устраняет необходимость в сложном конструкторе и устраняет необходимость беспокоиться о кешировании сериализатора. Другой вариант есть и поддерживается полностью.


Повторите следующие вопросы:

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

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

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

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

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

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

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

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

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

как xml формы:

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

Если вы хотите это сделать, добавьте 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;}
}

Это также относится ко всем другим мелочам вроде:

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

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