4

I have looked for examples to ignore a property of a class during xml serialization and deserialization. I have found three different methods and can't figure out, when they should be used. My special interest is, which one works with XmlSerializer better.

  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; }
    }
    

Where there is some explanation about the difference between XmlIgnoreAttribtue and NonSerializedAttribute on stackoverflow and msdn, I was not able to find information about when to use XmlIgnoreAttribtue and when the ShouldSerializeXXX pattern. I tried both of them with the XmlSerializer and both of them see work as expected.

dbc
  • 104,963
  • 20
  • 228
  • 340
scher
  • 1,813
  • 2
  • 18
  • 39
  • If they all work to your standard, then choose the one you prefer. PS The `ShouldSerializeXXX` version is an awful idea, don't use that one! – DavidG Jul 19 '17 at 11:47
  • @DavidG: Con you please argument why is it an awful idea to use `ShouldSerializeXXX`. – scher Jul 19 '17 at 12:08
  • Well you're going to pollute your model with all those extra methods whereas an attribute won't. – DavidG Jul 19 '17 at 12:12
  • Another reason to not use ShouldSerializeXXX is after implementing all of that, what happens when someone later comes in and needs to change the name of the field. That task now broke serialization without the developer being aware of it. – SASS_Shooter Jul 19 '17 at 16:21
  • XmlSerializer doesn't seem to have a ContractResolver like JSON.net, which leaves ShouldSerialize as the only way to dynamically include/exclude members. We use a T4 template to generate the ShouldSerializeXxxx functions. We have so many properties the generated file is 10,000 lines of code. – Brain2000 Nov 05 '19 at 20:53

1 Answers1

4

The basic difference between #1 and #2 is that they generate different XML Schemas. If you want a member to be excluded from your type's schema, use [XmlIgnore]. If you want a member to be included conditionally, use ShouldSerializeXXX() or XXXSpecified. (Finally, as stated in this answer, [NonSerialized] in option #3 is ignored by XmlSerializer.)

To see the difference between #1 and #2, you can use xsd.exe to generate schemas for your types. The following schema is generated for version #1 and completely omits the Name member:

<xs:complexType name="Item" />

While the following for #2 conditionally includes the Name member:

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

The difference arises because XmlSerializer and xsd.exe both perform static type analysis rather than dynamic code analysis. Neither tool can determine that the Name property in case #2 will always be skipped, because neither tool attempts to decompile the source code for ShouldSerializeName() to prove it always returns false. Thus Name will appear in the schema for version #2 despite never appearing in practice. If you then create a web service and publish your schema with WSDL (or simply make them available manually), different clients will be generated for these two types -- one without a Name member, and one with.

An additional complexity can arise when the property in question is of a non-nullable value type. Consider the following three versions of Item. Firstly, a version with an unconditionally included value property:

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

Generates the following schema with Id always present:

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

Secondly, a version with an unconditionally excluded value property:

public class Item
{
    [XmlIgnore]
    public int Id { get; set; }
}

Generates the following schema that completely omits the Id property:

  <xs:complexType name="Item" />

And finally a version with a conditionally excluded value property:

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

    public bool ShouldSerializeId()
    {
        return false;
    }
}

Generates the following schema with Id only conditionally present:

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

Schema #2 is as expected, but notice there is a difference between #1 and #3: the first has minOccurs="1" while the third has minOccurs="0". The difference arises because XmlSerializer is documented to skip members with null values by default, but has no similar logic for non-nullable value members. Thus the Id property in case #1 will always get serialized, and so minOccurs="1" is indicated in the schema. Only when conditional serialization is enabled will minOccurs="0" be generated. If the third schema is in turn used for client code generation, an IdSpecified property will be added to the auto-generated code to track whether the Id property was actually encountered during deserialization:

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;
        }
    }
}

For more details on binding to conditionally serialized value members, see XML Schema Binding Support: MinOccurs Attribute Binding Support and ShouldSerialize*() vs *Specified Conditional Serialization Pattern.

So that's the primary difference, but there are secondary differences as well that may influence which you choose:

  • [XmlIgnore] cannot be overridden in a derived class, but ShouldSerializeXXX() can be when marked as virtual; see here for an example.

  • If a member cannot be serialized by XmlSerializer because, for instance, it refers to a type that lacks a parameterless constructor, then marking the member with [XmlIgnore] will allow the containing type to be serialized - while adding a ShouldSerializeXXX() { return false; } will NOT allow the containing type to be serialized, since as stated previously XmlSerializer only performs static type analysis. E.g. the following:

     public class RootObject
     {
         // This member will prevent RootObject from being serialized by XmlSerializer despite the fact that the ShouldSerialize method always returns false.
         // To make RootObject serialize successfully, [XmlIgnore] must be added.
         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; }
     }
    

    cannot be serialized by XmlSerializer.

  • [XmlIgnore] is specific to XmlSerializer, but ShouldSerializeXXX() is used by other serializers including Json.NET and protobuf-net.

  • As mentioned in comments, renaming a conditionally serialized property in Visual Studio does not automatically rename the corresponding ShouldSerializeXXX() method name, leading to potential maintenance gotchas down the road.

riQQ
  • 9,878
  • 7
  • 49
  • 66
dbc
  • 104,963
  • 20
  • 228
  • 340