ShouldSerialize *() vs * Условный шаблон условной сериализации

Я знаю как шаблон ShouldSerialize *, так и заданный шаблон и то, как они работают, но есть ли разница между ними?

Есть ли какие-либо "gotchas", используя один метод против другого, когда определенные вещи должны быть сериализованы условно?

Этот вопрос специфичен для использования XmlSerializer, но общая информация по этой теме также приветствуется.

Здесь очень мало информации по этой теме, так что это может быть потому, что они выполняют ту же самую цель, и это выбор стиля. Тем не менее, кажется странным, что разработчики .NET анализировали класс с помощью рефлексии и искали оба/оба шаблона, чтобы определить, как ведет себя сгенерированный сериализатор, поскольку он замедляет генерацию сериализатора, если это не просто артефакт обратной совместимости.

EDIT: Для тех, кто не знаком с двумя шаблонами, если свойство *Specified или метод ShouldSerialize* возвращает true, тогда это свойство сериализуется.

public string MyProperty { get; set; }

//*Specified Pattern
[XmlIgnore]
public bool MyPropertySpecified { get{ return !string.IsNullOrWhiteSpace(this.MyProperty); } }

//ShouldSerialize* Pattern
public bool ShouldSerializeMyProperty()
{
     return !string.IsNullOrWhiteSpace(this.MyProperty);
}

Ответ 1

Цель шаблона {propertyName}Specified описана в Поддержка привязки XML-схемы: поддержка привязки атрибутов MinOccurs. Он был добавлен для поддержки элемента схемы XSD, в котором:

  • Используется элемент <element>.
  • minOccurs равен нулю.
  • Атрибут maxOccurs задает один экземпляр.
  • Тип данных преобразуется в тип значения.

В этом случае xsd.exe /classes будет автоматически генерировать (или вы можете вручную генерировать) свойство с тем же именем, что и элемент схемы, и a {propertyName}Specified boolean get/set свойство, которое отслеживает, был ли элемент встречен в XML, и должен быть сериализован обратно в XML. Если элемент встречается, {propertyName}Specified устанавливается на true, в противном случае false. Таким образом, десериализованный экземпляр может определить, было ли свойство отменено или явно установлено его значение по умолчанию во время десериализации.

Обратный также реализуется для генерации схемы. Если вы определяете тип С# с парой свойств, соответствующих шаблону выше, используйте xsd.exe для создания соответствующего файла XSD, в схему будет добавлен соответствующий minOccurrs. Например, задан следующий тип:

public class ExampleClass
{
    [XmlElement]
    public decimal Something { get; set; }

    [XmlIgnore]
    public bool SomethingSpecified { get; set; }
}

Будет создана следующая схема и наоборот:

<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="ExampleClass" nillable="true" type="ExampleClass" />
  <xs:complexType name="ExampleClass">
    <xs:sequence>
      <xs:element minOccurs="0" maxOccurs="1" name="Something" type="xs:decimal" />
    </xs:sequence>
  </xs:complexType>
</xs:schema>

Обратите внимание, что, хотя xsd.exe документируется только для автоматического создания свойства {propertyName}Specified для свойств типа значения, XmlSerializer будет уважать шаблон при использовании вручную для свойств ссылочного типа.

Вы можете спросить, почему xsd.exe не связывается с нулевым значением в этом случае? Возможно, потому что:

Вам нужно знать этот шаблон, потому что xsd.exe иногда генерирует его для вас автоматически, однако взаимодействие между свойством и свойством Specified является странным и способным создавать ошибки. Вы можете заполнить все свойства в своем классе, а затем сериализовать XML и потерять все, потому что вы также не установили соответствующие свойства Specified на true. Эта "гоча" появляется здесь время от времени здесь, см., Например, этот вопрос или этот тоже.

Еще одна "полученная" с этим шаблоном заключается в том, что если вам нужно сериализовать свой тип с помощью сериализатора, который не поддерживает этот шаблон, вы можете > вручную отключить вывод этого свойства во время сериализации, и , вероятно, будет необходимо вручную установить его во время десериализации. Поскольку каждый сериализатор может иметь свой собственный собственный механизм для подавления свойств (или вообще никакого механизма!), Это может стать все более обременительным с течением времени.

(Наконец, я немного удивлен, что ваш MyPropertySpecified работает успешно без сеттера. Кажется, я вспоминаю версию .Net 2.0, в которой отсутствующий установщик {propertyName}Specified может вызвать исключение. он больше не воспроизводится в более поздних версиях, и у меня нет 2.0 для тестирования. Так что это может быть третий вопрос.)

Поддержка метода ShouldSerialize{PropertyName}() задокументирована в Свойствах в элементах управления Windows Forms: определение значений по умолчанию с помощью методов ShouldSerialize и Reset. Как видите, документация находится в разделе Windows Forms в MSDN, а не в разделе XmlSerializer, так что это, по сути, полузакрытая функциональность. Я не знаю, почему поддержка этого метода и свойство Specified существуют в XmlSerializer. ShouldSerialize был введен в .NET 1.1, и я считаю, что поддержка привязки MinOccurs была добавлена ​​в .NET 2.0, поэтому, возможно, более ранняя функциональность не вполне соответствовала потребностям (или вкусу) команды разработчиков xsd.exe?

Поскольку это метод, а не свойство, ему не хватает "gotchas" шаблона {propertyName}Specified. Он также кажется более популярным на практике и был принят другими сериализаторами, включая:

Итак, какой шаблон использовать?

  • Если xsd.exe автоматически генерирует свойство {propertyName}Specified, или ваш тип должен отслеживать, появился или нет какой-либо конкретный элемент в XML файле, или вам нужен автогенератор XSD, чтобы указать, что определенное значение является необязательным, используйте этот шаблон и следите за "gotchas".

  • В противном случае используйте шаблон ShouldSerialize{PropertyName}(). Он имеет меньше ошибок и может быть более широко поддержан.

Ответ 2

Чтобы добавить к очень подробному ответу @dbc, я столкнулся с проблемой управления сериализацией в производных классах. В моей ситуации у меня был базовый класс и производный класс, в котором свойство Prop было переопределено.

public class BaseClass
{
    public virtual string Prop {get; set;}
}

public class Derived: BaseClass
{
    public string Comp1 {get; set;}
    public string Comp2 {get; set;}
    public override string Prop {get => Comp1 + Comp2; set {}}
}

Поскольку вычисляется свойство Prop в производном классе, для класса Derived я хотел сериализовать Comp1 и Comp2, но не Prop. Оказывается, установка атрибута XmlIgnore в свойстве Prop в классе Derived не работает, а Prop сериализована.

Я также попытался добавить метод ShouldSerializeProp и свойство PropSpecified в классе Derived, но не работает. Я попытался установить точки останова, чтобы узнать, вызваны ли они, а они нет.

Оказывается, что XmlSerializer смотрит на исходный класс, где свойство Prop впервые появляется в иерархии классов, чтобы решить, сериализовать свойство или нет. Чтобы иметь возможность управлять сериализацией в производном классе, сначала мне пришлось добавить virtual ShouldSerializeProp в класс Base.

public class Base
{
    .....
    public virtual bool ShouldSerializeProp() {return true;}
}

Затем я мог бы переопределить ShouldSerializeProp в классе Derived и вернуть false.

public class Derived: Base
{
    .....
    public override bool ShouldSerializeProp() {return false;}
}

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