ShouldSerialize*() 与 *Specified 条件序列化模式

ShouldSerialize*() vs *Specified Conditional Serialization Pattern

我知道 ShouldSerialize* 模式和 *Specified 模式以及它们的工作原理,但两者之间有什么区别吗?

当某些东西应该有条件地序列化时,是否有 "gotchas" 使用一种方法与另一种方法的区别?

此问题特定于 XmlSerializer 的用法,但也欢迎提供有关此主题的一般信息。

关于这个主题的信息很少,所以这可能是因为它们执行完全相同的目的并且这是一种风格选择。但是,.NET 实现者通过反射分析 class 并寻找 either/both 模式来确定生成的序列化程序的行为方式似乎很奇怪,因为它减慢了序列化程序的生成速度,除非它只是向后兼容性神器。

编辑: 对于那些不熟悉这两种模式的人,如果 *Specified 属性 或 ShouldSerialize* 方法 returns 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);
}

{propertyName}Specified 模式的意图记录在 XML Schema Binding Support: MinOccurs Attribute Binding Support 中。添加它是为了支持 XSD 架构元素,其中:

  • 涉及<element>元素。
  • minOccurs 为零。
  • maxOccurs 属性指定单个实例。
  • 数据类型转换为值类型。

在这种情况下,xsd.exe /classes 将自动生成(或者您可以手动生成)一个与架构元素同名的 属性 和一个 {propertyName}Specified 布尔值 get/set 属性 跟踪是否在 XML 中遇到了元素并且应该序列化回 XML。 如果遇到元素,{propertyName}Specified 设置为 true,否则 false。因此,反序列化的实例可以确定 属性 在原始 XML.

中是否未设置(而不是明确设置为其默认值)

还为模式生成实现了逆向。如果你定义了一个 C# 类型,它的一对属性匹配上面的模式,然后使用 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 不绑定到 Nullable<T>?可能是因为:

您需要注意此模式,因为 xsd.exe 有时会自动为您生成它,但是 属性 与其 Specified 属性 之间的交互是很奇怪,容易产生错误。您可以在 class 中填写所有属性,然后序列化为 XML 并丢失 所有内容 因为您还没有设置相应的 Specified 属性到 true。这个 "gotcha" 不时出现在这里,参见例如this question or this one also.

此模式的另一个 "gotcha" 是,如果您需要使用不支持此模式的序列化程序序列化您的类型,您 可能 想要手动抑制这个 属性 在序列化期间的输出,并且 可能 需要在反序列化期间手动设置它。由于每个序列化程序都可能有自己的自定义机制来抑制属性(或者根本没有机制!),随着时间的推移,这样做会变得越来越麻烦。

(最后,我有点惊讶你的 MyPropertySpecified 在没有 setter 的情况下也能成功运行。我似乎记得一个 .Net 2.0 版本,其中缺少 {propertyName}Specified setter 会导致抛出异常。但它在以后的版本中不再可重现,而且我没有 2.0 可以测试。所以这可能是第三个陷阱。)

ShouldSerialize{PropertyName}() 方法的支持记录在 Properties in Windows Forms Controls: Defining Default Values with the ShouldSerialize and Reset Methods. As you can see the documentation is in the Windows Forms section of MSDN not the XmlSerializer section, so it is, in fact, semi-hidden functionality. I have no idea why support for this method and the Specified property both exist in XmlSerializer. ShouldSerialize was introduced in .Net 1.1 and I believe that MinOccurs binding support was added in .Net 2.0 中,所以也许早期的功能不太符合 xsd.exe 开发团队的需求(或品味)?

因为它不是 属性 的方法,所以它缺少 {propertyName}Specified 模式的 "gotchas"。它在实践中似乎也更受欢迎,并已被其他序列化程序采用,包括:

那么,使用哪种模式?

  1. 如果xsd.exe自动为你生成一个{propertyName}Specified属性,或者你的类型需要跟踪特定元素是否出现在[=128中=] 文件,或者您需要自动生成的 XSD 来指示某个值是可选的,请使用此模式并注意 "gotchas".

  2. 否则,使用ShouldSerialize{PropertyName}()模式。它的陷阱更少,可能会得到更广泛的支持。

为了添加@dbc 的非常详细的答案,我 运行 解决了在派生 classes 中管理序列化的问题。在我的情况下,我有一个基础 class 和一个派生的 class,其中 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 {}}
}

由于推导的class中的Prop属性是计算出来的,对于Derivedclass我想序列化Comp1Comp2 但不是 Prop。原来在DerivedclassProp属性上设置XmlIgnore属性是不行的,反正Prop序列化了。

我还尝试在 Derived class 中添加一个 ShouldSerializeProp 方法和一个 PropSpecified 属性,但都不起作用。我尝试设置断点以查看它们是否被调用并且没有被调用。

事实证明,XmlSerializer 正在查看原始 class,其中 Prop 属性 首次出现在 class 层次结构中决定是否序列化 属性 或不。为了能够在派生的 class 中控制序列化,首先我必须在 Base class 中添加一个 virtual ShouldSerializeProp

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

然后我可以覆盖 Derived class 和 return 中的 ShouldSerializeProp false。

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

此模式允许不同的派生 classes 选择它们序列化的父 class 的属性。希望这可以帮助。