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

32

我知道 ShouldSerialize* 模式和 *Specified 模式以及它们的工作原理,但这两种模式之间有什么区别吗?使用其中一种方法与另一种方法相比,在某些情况下需要有条件地序列化时,是否存在任何细节问题?此问题特定于 XmlSerializer 的用法,但欢迎提供关于此主题的一般信息。由于这方面的信息很少,因此可能是因为它们具有完全相同的目的,并且这是风格选择的原因。但是,似乎奇怪的是,.NET 实现者会通过反射分析类并查找一个或多个模式来确定生成的序列化程序的行为方式,因为它会减慢序列化程序的生成速度,除非它只是向后兼容的产物。

编辑:对于那些不熟悉这两种模式的人,如果 *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
什么是“指定模式”?您是否指的是[XmlIgnore]属性? - Sinatr
1
@Sinatr 不需要。如果你创建了一个可获取的布尔属性,命名为<Propertyname>Specified,并返回true以便序列化,它看起来与ShouldSerialize*模式做的事情相同(显然,你需要在此属性上放置一个[XmlIgnore]属性)。 - JNYRanger
4
这是有意为之不记录文档,他们希望您使用属性来代替。这个“功能”可能因为早期版本的Winforms设计师或未完成的计划移动到类似XAML的东西而偷偷潜入XmlSerializer中。这只是一个猜测。 - Hans Passant
2
@HansPassant 有趣。在 XmlSerializer 类文档的“控制生成的 XML”部分中,指定模式被简要提到这里,但是没有关于 ShouldSerialize() 模式的内容。不幸的是,当序列化是有条件的时,属性无法起作用。 - JNYRanger
2个回答

46

{propertyName}Specified模式的意图在XML Schema绑定支持:MinOccurs属性绑定支持中有记录。 它被添加以支持具有以下特征的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 属性之间的交互很奇怪,容易产生错误。你可以填充类中的所有属性,然后将其序列化为 XML,并且因为你没有设置相应的 Specified 属性为 true,而失去了 所有内容。这个“坑”在这里不时出现,例如请参见 this questionthis one also

这种模式的另一个“陷阱”是,如果您需要使用不支持此模式的序列化器对类型进行序列化,则可能需要在序列化期间手动抑制此属性的输出,并且在反序列化期间可能会需要手动设置它。由于每个序列化器可能都有自己的自定义机制来抑制属性(或根本没有机制!),因此随着时间的推移,这样做可能变得越来越繁琐。

(最后,我有点惊讶您的MyPropertySpecified可以在没有setter的情况下成功工作。我似乎记得在某个 .Net 2.0 版本中,缺少{propertyName}Specified setter 会导致抛出异常。但在后来的版本中无法再现,而我也没有 2.0 版本进行测试。所以这可能是第三个“陷阱”。)

对于ShouldSerialize{PropertyName}()方法的支持在Windows Forms控件的属性:使用ShouldSerialize和Reset方法定义默认值中有详细说明。正如你所看到的,该文档位于MSDN的Windows Forms部分,而不是XmlSerializer部分,因此它实际上是半隐藏的功能。我不知道为什么XmlSerializer同时存在这个方法和Specified属性的支持。ShouldSerialize是在.Net 1.1中引入的,我相信MinOccurs绑定支持是在.Net 2.0中添加的,所以也许早期的功能没有完全满足xsd.exe开发团队的需求(或口味)?

因为它是一种方法而不是属性,所以它缺乏 "{propertyName}Specified" 模式的陷阱。此外,在实践中,这种方法似乎更受欢迎,并已被其他序列化器采用,包括:

那么,应该使用哪种模式呢?

  1. 如果 xsd.exe 自动为您生成了 {propertyName}Specified 属性,或者您的类型需要跟踪 XML 文件中是否出现了特定元素,或者您需要您自动生成的 XSD 表示某个值是可选的,请使用此模式并注意其中的陷阱。

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


感谢您提供详细的答案! - JNYRanger

8

补充一下@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类,我想序列化Comp1Comp2但不包括Prop。结果发现,在Derived类中将XmlIgnore属性设置为Prop属性无效,Prop仍然被序列化。
我还尝试在Derived类中添加一个ShouldSerializeProp方法和一个PropSpecified属性,但都没有成功。我尝试设置断点查看它们是否被调用,但没有。
事实证明,XmlSerializer查看Prop属性第一次出现在类层次结构中的原始类,以决定是否序列化属性。要能够控制派生类中的序列化,首先必须在Base类中添加一个virtual ShouldSerializeProp
public class Base
{
    .....
    public virtual bool ShouldSerializeProp() {return true;}
}

那么我可以在Derived类中重写ShouldSerializeProp方法并返回false。

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

这种模式允许不同的派生类选择从父类中序列化哪些属性。希望这可以帮助你理解。


很好的观点@Tibi。我几年前就问过这个问题,但从那时起我学到的是,除非你事先知道需要虚拟方法而必须有派生类,否则始终将序列化类设置为密封的。 - JNYRanger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接