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.
ShouldSerializeXXXversion is an awful idea, don't use that one!ShouldSerializeXXX.