何时使用ShouldSerializeXXX和XmlIgnoreAttribute进行XML序列化

4

我查看了一些示例,以忽略类的属性在xml序列化和反序列化期间。我找到了三种不同的方法,但无法确定它们应该在何时使用。我特别关注哪种方法与XmlSerializer 更兼容。

  1. XmlIgnore attribute

    public class Item
    {
        [XmlIgnore]
        public string Name { get; set; }
    }
    
  2. Method beginning with ShouldSerialize...

    public class Item
    {
        public string Name { get; set; }
    
        public bool ShouldSerializeName()
        {
            return false;
        }
    }
    
  3. NonSerialized attribute

    public class Item
    {
        [NonSerialized]
        public string Name { get; set; }
    }
    

stackoverflowmsdn上有关于XmlIgnoreAttribtueNonSerializedAttribute之间区别的一些解释,但我无法找到关于何时使用XmlIgnoreAttribtueShouldSerializeXXX模式的信息。我尝试了这两种方法与XmlSerializer一起使用,它们都能正常工作。


如果它们都符合您的标准,那么选择您喜欢的版本。顺便说一句,“ShouldSerializeXXX”版本是一个糟糕的想法,请不要使用它! - DavidG
@DavidG:你能否解释一下为什么使用ShouldSerializeXXX是一个糟糕的想法。 - scher
你会用所有这些额外的方法污染你的模型,而属性不会。 - DavidG
不使用ShouldSerializeXXX的另一个原因是,当实现所有这些后,如果有人后来需要更改字段的名称,那么会发生什么。此时,该任务会破坏序列化,而开发人员却没有意识到。 - SASS_Shooter
XmlSerializer似乎没有像JSON.net那样的ContractResolver,这就只能通过ShouldSerialize来动态地包含/排除成员。我们使用T4模板生成ShouldSerializeXxxx函数。由于我们有太多的属性,所以生成的文件有10,000行代码。 - Brain2000
1个回答

4
#1和#2之间的基本区别在于它们生成不同的XML模式。如果你想要一个成员被排除在类型的模式之外,请使用[XmlIgnore]。如果你想要一个成员有条件地被包含,请使用ShouldSerializeXXX()XXXSpecified。(最后,正如this answer所述,选项#3中的[NonSerialized]XmlSerializer忽略。)
要查看#1和#2之间的区别,您可以使用{{link1: xsd.exe }}为您的类型生成模式。 以下模式是为版本#1生成的,并完全省略了 Name 成员:
<xs:complexType name="Item" />

在以下 #2 的条件中,有条件地包括了 Name 成员:

<xs:sequence>
  <xs:element minOccurs="0" maxOccurs="1" name="Name" type="xs:string" />
</xs:sequence>

差异是由于XmlSerializerxsd.exe都执行静态类型分析而不是动态代码分析。 两个工具都无法确定情况#2中的Name属性将始终被跳过,因为两个工具都不尝试反编译ShouldSerializeName()的源代码以证明它总是返回false。 因此,Name将出现在版本#2的模式中,尽管实际上从未出现过。 如果您随后创建Web服务并使用{{link1:WSDL}}发布模式(或仅手动使其可用),则将为这两种类型生成不同的客户端-一个没有Name成员,另一个有。
当涉及到非空值类型的属性时,可能会出现额外的复杂性。 考虑以下三个版本的Item。 首先,一个包含无条件包含值属性的版本:
public class Item
{
    public int Id { get; set; }
}

生成以下架构,并始终包含Id
  <xs:complexType name="Item">
    <xs:sequence>
      <xs:element minOccurs="1" maxOccurs="1" name="Id" type="xs:int" />
    </xs:sequence>
  </xs:complexType>

其次,一个带有无条件排除值属性的版本:
public class Item
{
    [XmlIgnore]
    public int Id { get; set; }
}

生成以下架构,完全省略Id属性:
  <xs:complexType name="Item" />

最后,附带一个条件排除值属性的版本:

public class Item
{
    public int Id { get; set; }

    public bool ShouldSerializeId()
    {
        return false;
    }
}

生成以下模式,只有在条件下出现Id

  <xs:complexType name="Item">
    <xs:sequence>
      <xs:element minOccurs="0" maxOccurs="1" name="Id" type="xs:int" />
    </xs:sequence>
  </xs:complexType>

模式 #2 符合预期,但请注意 #1 和 #3 之间的区别:第一个具有 minOccurs="1",而第三个具有 minOccurs="0"。这种差异是因为 XmlSerializer 在默认情况下被 documented 跳过具有 null 值的成员,但对于非空值成员没有类似的逻辑。因此,在情况 #1 中,Id 属性将始终被序列化,所以在模式中指示了 minOccurs="1"。只有在启用条件序列化时,才会生成 minOccurs="0"。如果反过来使用第三个模式用于客户端代码生成,则会向自动生成的代码添加一个 IdSpecified 属性,以跟踪是否在反序列化期间实际遇到了 Id 属性:

public partial class Item {

    private int idField;

    private bool idFieldSpecified;

    /// <remarks/>
    public int Id {
        get {
            return this.idField;
        }
        set {
            this.idField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlIgnoreAttribute()]
    public bool IdSpecified {
        get {
            return this.idFieldSpecified;
        }
        set {
            this.idFieldSpecified = value;
        }
    }
}

有关绑定到条件序列化值成员的更多详细信息,请参阅XML Schema绑定支持:MinOccurs属性绑定支持ShouldSerialize*() vs *Specified条件序列化模式

那么这就是主要区别,但也有次要差异可能会影响你的选择:

  • [XmlIgnore] 不能在派生类中被覆盖,但是当标记为虚拟的时候ShouldSerializeXXX() 可以被覆盖; 请参见 这里 中的示例。

  • 如果一个成员无法被XmlSerializer序列化,因为它引用了缺乏无参数构造函数的类型,那么将该成员标记为[XmlIgnore]将允许包含的类型被序列化——而添加 ShouldSerializeXXX() { return false; }则不会允许包含类型被序列化,因为如前所述,XmlSerializer只执行静态类型分析。例如以下内容:

    public class RootObject
     {
         // 此成员将阻止 RootObject 通过 XmlSerializer 进行序列化,尽管 ShouldSerialize 方法始终返回 false。
         // 要使 RootObject 成功序列化,必须添加 [XmlIgnore]。
         public NoDefaultConstructor NoDefaultConstructor { get; set; }
    
         public bool ShouldSerializeNoDefaultConstructor() { return false; }
     }
    
     public class NoDefaultConstructor
     {
         public string Name { get; set; }
         public NoDefaultConstructor(string name) { this.Name = name; }
     }
     

    不能被XmlSerializer序列化。

  • [XmlIgnore]是特定于XmlSerializer,但是ShouldSerializeXXX()被其他序列化器使用,包括Json.NETprotobuf-net

  • 如在注释中提到的那样,在Visual Studio中重命名有条件地进行序列化的属性不会自动重命名相应的ShouldSerializeXXX()方法名称,这可能会导致潜在的维护问题。


1
非常感谢您详细的解释。这为我提供了完美的决策指导。太棒了。 - scher

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