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>
?可能是因为:
- 可空值用于支持
xsi:nil="true"
attribute instead. See Xsi:nil Attribute Binding Support。
- 直到 .Net 2.0 才引入可空值,所以现在使用它们可能为时已晚?
您需要注意此模式,因为 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"。它在实践中似乎也更受欢迎,并已被其他序列化程序采用,包括:
- Json.NET
- protobuf-net (which claims to supports both patterns.)
那么,使用哪种模式?
如果xsd.exe
自动为你生成一个{propertyName}Specified
属性,或者你的类型需要跟踪特定元素是否出现在[=128中=] 文件,或者您需要自动生成的 XSD 来指示某个值是可选的,请使用此模式并注意 "gotchas".
否则,使用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
属性是计算出来的,对于Derived
class我想序列化Comp1
和Comp2
但不是 Prop
。原来在Derived
classProp
属性上设置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 的属性。希望这可以帮助。
我知道 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>
?可能是因为:
- 可空值用于支持
xsi:nil="true"
attribute instead. See Xsi:nil Attribute Binding Support。 - 直到 .Net 2.0 才引入可空值,所以现在使用它们可能为时已晚?
您需要注意此模式,因为 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"。它在实践中似乎也更受欢迎,并已被其他序列化程序采用,包括:
- Json.NET
- protobuf-net (which claims to supports both patterns.)
那么,使用哪种模式?
如果
xsd.exe
自动为你生成一个{propertyName}Specified
属性,或者你的类型需要跟踪特定元素是否出现在[=128中=] 文件,或者您需要自动生成的 XSD 来指示某个值是可选的,请使用此模式并注意 "gotchas".否则,使用
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
属性是计算出来的,对于Derived
class我想序列化Comp1
和Comp2
但不是 Prop
。原来在Derived
classProp
属性上设置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 的属性。希望这可以帮助。