.net ShouldSerialize*()与 * 指定的条件序列化模式

jm81lzqq  于 2023-03-20  发布在  .NET
关注(0)|答案(2)|浏览(96)

我知道ShouldSerialize* 模式和 *Specified模式以及它们是如何工作的,但是这两者之间有什么区别吗?
当某些东西应该有条件地序列化时,使用一种方法与使用另一种方法是否存在“陷阱”?
这个问题专门针对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);
}
ijxebb2r

ijxebb2r1#

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

  • 涉及到**<element>**元素。
    *最小发生次数为零。
    *maxOccurs属性指示单个示例。
  • 数据类型转换为值类型。

在这种情况下,xsd.exe /classes将自动生成(或者您可以手动生成)与架构元素同名的属性和{propertyName}Specified布尔get/set属性 ,该属性跟踪在XML中是否遇到该元素以及是否应该将其序列化回XML。 如果遇到该元素,则将{propertyName}Specified设置为true,因此,反序列化后的示例可以确定该属性在原始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"属性。请参见Xsi:nil Attribute Binding Support
  • Nullables直到.Net 2.0才被引入,所以现在使用它们来实现这个目的可能已经太晚了吧?

您需要注意这种模式,因为xsd.exe有时会自动为您生成它,但是属性和它的Specified属性之间的交互很奇怪,容易产生bug。然后序列化为XML并丢失 * 所有内容 *,因为您还没有将相应的Specified属性设置为true。参见例如X1 E3 F1 X或X1 E4 F1 X。
此模式的另一个“陷阱”是,如果您需要使用不支持此模式的序列化程序序列化类型,则可能希望在序列化期间手动取消此属性的输出,并且可能需要在反序列化期间手动设置它。由于每个序列化程序都可能有自己的自定义机制来取消属性(或者根本没有机制!),随着时间的推移,这样做会变得越来越繁重。
(最后,我对MyPropertySpecified在没有setter的情况下也能成功工作感到有点惊讶。我好像记得.Net 2.0的一个版本中,缺少{propertyName}Specified setter会导致抛出异常。但它在以后的版本中不再可重现,而且我也没有2.0可供测试。所以这可能是第三个问题。)
Properties in Windows Forms Controls: Defining Default Values with the ShouldSerialize and Reset Methods中记录了对ShouldSerialize{PropertyName}()方法的支持,正如您所看到的,该文档位于MSDN的Windows窗体部分,而不是XmlSerializer部分,因此,实际上,我不知道为什么在XmlSerializer中同时存在对这个方法和Specified属性的支持。ShouldSerialize是在.Net 1.1中引入的,我 * 相信 * MinOccurs绑定支持是在.Net 2.0中添加的,所以也许早期的功能并不完全满足xsd.exe开发团队的需求(或口味)?
因为它是一个方法而不是属性,所以它没有{propertyName}Specified模式中的“陷阱”。它在实践中似乎也更受欢迎,并已被其他序列化程序采用,包括:

那么,该使用哪种模式呢?
1.如果xsd.exe为您自动生成{propertyName}Specified属性,或者您的类型需要跟踪特定元素是否出现在XML文件中,或者您需要自动生成的XSD来指示某个值是可选的,那么请使用此模式并注意“陷阱”。
1.否则,请使用ShouldSerialize{PropertyName}()模式,它的缺陷较少,可能得到更广泛的支持。

ufj5ltwl

ufj5ltwl2#

为了补充@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类中的Prop属性上设置XmlIgnore属性不起作用,并且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;}
}

这个模式允许不同的派生类选择它们序列化父类中的哪些属性。希望这能有所帮助。

相关问题