Shouldserialize*() VS *Specified Conditional Serialization Pattern

ShouldSerialize*() vs *Specified Conditional Serialization Pattern

The intent of the {propertyName}Specified pattern is documented in XML Schema Binding Support: MinOccurs Attribute Binding Support. It was added to support an XSD schema element in which:

  • The <element> element is involved.
  • minOccurs is zero.
  • The maxOccurs attribute dictates a single instance.
  • The data type converts to a value type.

In this case, xsd.exe /classes will automatically generate (or you can manually generate) a property with the same name as the schema element and a {propertyName}Specified boolean get/set property that tracks whether the element was encountered in the XML and should be serialized back to XML. If the element is encountered, {propertyName}Specified is set to true, otherwise false. Thus the deserialized instance can determine whether the property was unset (rather than explicitly set to its default value) in the original XML.

The inverse is also implemented for schema generation. If you define a C# type with a pair of properties matching the pattern above, then use xsd.exe to generate a corresponding XSD file, an appropriate minOccurrs will be added to the schema. For instance, given the following type:

public class ExampleClass
{
[XmlElement]
public decimal Something { get; set; }

[XmlIgnore]
public bool SomethingSpecified { get; set; }
}

The following schema will be generated, and vice versa:

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

Note that, while xsd.exe is documented only to generate automatically a {propertyName}Specified property for value type properties, XmlSerializer will respect the pattern when used manually for reference type properties.

You might ask, why does xsd.exe not bind to a Nullable<T> in this case? Perhaps because:

  • Nullables are used to support the xsi:nil="true" attribute instead. See Xsi:nil Attribute Binding Support.
  • Nullables were not introduced until .Net 2.0, so maybe it was too late to use them for this purpose?

You need to be aware of this pattern because xsd.exe will sometimes generate it for you automatically, however the interaction between a property and its Specified property is weird and liable to produce bugs. You can fill up all the properties in your class, then serialize to XML and lose everything because you didn't also set set the corresponding Specified properties to true. This "gotcha" comes up here from time to time here, see e.g. this question or this one also.

Another "gotcha" with this pattern is that, if you need to serialize your type with a serializer that does not support this pattern, you may want to manually suppress output of this property during serialization, and probably will need to manually set it during deserialization. Since each serializer may have its own custom mechanism for suppressing properties (or no mechanism at all!), doing this can become more and more burdensome over time.

(Finally, I'm a little surprised that your MyPropertySpecified works successfully without a setter. I seem to recall a version of .Net 2.0 in which a missing {propertyName}Specified setter would cause an exception to be thrown. But it's no longer reproducible on later versions, and I don't have 2.0 to test. So that might be a third gotcha.)

Support for the ShouldSerialize{PropertyName}() method is documented in 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, so perhaps the earlier functionality didn't quite meet the needs (or taste) of the xsd.exe development team?

Because it is a method not a property, it lacks the "gotchas" of the {propertyName}Specified pattern. It also seems to be more popular in practice, and has been adopted by other serializers including:

  • Json.NET
  • protobuf-net (which claims to supports both patterns.)

So, which pattern to use?

  1. If xsd.exe generates a {propertyName}Specified property for you automatically, or your type needs to track whether a specific element appeared or not in the XML file, or you need your auto-generated XSD to indicate that a certain value is optional, use this pattern and watch out for the "gotchas".

  2. Otherwise, use the ShouldSerialize{PropertyName}() pattern. It has fewer gotchas and may be more widely supported.

When to use ShouldSerializeXXX vs. XmlIgnoreAttribute for XML serialization

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.

ShouldSerialize... mechanism seems to work no more

When used with XmlSerializer, the method needs to be public, i.e.

public bool ShouldSerializeConfiguration()
{
return serializeConfig;
}

This isn't the case for some other scenarios, but XmlSerializer essentially works as an independently compiled assembly, so full visibility rules apply.

protobuf.net & conditional serialization

It is a good question. The patterns currently supported (ShouldSerialize* etc) are borrowed "as is" entirely from the BCL, hence no context - however there is no reason it can't support parameters in the same way that the callbacks do - indeed, for callbacks it supports pretty much any usage (with/without context etc) - so I can't think of a good reason not to support them here to.

You are right t say it isn't supported currently, but it could be - let me know of this would be useful.

Xml Serialization Issues in Ordering and XMLRoot attributes

The first issue is on the XmlRoot element. I expected version attribute to appear inside the XMLRoot tag but it does not.

As explained in Introducing XML Serialization, XmlSerializer will not serialize a const member even when public:

XML serialization serializes only the public fields and property values of an object into an XML stream. XML serialization does not include type information.

<snip>

XML serialization does not convert methods, indexers, private fields, or read-only properties (except read-only collections). To serialize all an object's fields and properties, both public and private, use the DataContractSerializer instead of XML serialization.

The easiest way to work around this is to add a surrogate property for the version, like so:

public const string version = "D";

[XmlAttribute("version")]
[System.ComponentModel.Browsable(false), System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never), System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
public string Version { get { return version; } set { /* Do nothing */ } }

The property has to be public, but you can decrease its visibility via the attributes Browsable, EditorBrowsable and DebuggerBrowsable.

The second issue is the order of elements (ok it's not a big issue since tags are not order dependent) I tried to use XmlElement(Order=nn) attribute to force it but I just got a number of Reflection exceptions.

You didn't provide an example of this, but I was able to reproduce the following issue. If I set XmlArrayAttribute.Order on some but not all of the collection members of SchematicExport, I got the following exception:

System.InvalidOperationException: There was an error reflecting type 'SchematicExport'. 
---> System.InvalidOperationException: Inconsistent sequencing: if used on one of the class's members, the 'Order' property is required on all particle-like members, please explicitly set 'Order' using XmlElement, XmlAnyElement or XmlArray custom attribute on class member 'nets'.
at System.Xml.Serialization.XmlReflectionImporter.InitializeStructMembers(StructMapping mapping, StructModel model, Boolean openModel, String typeName, RecursionLimiter limiter)
at System.Xml.Serialization.XmlReflectionImporter.ImportStructLikeMapping(StructModel model, String ns, Boolean openModel, XmlAttributes a, RecursionLimiter limiter)
at System.Xml.Serialization.XmlReflectionImporter.ImportTypeMapping(TypeModel model, String ns, ImportContext context, String dataType, XmlAttributes a, Boolean repeats, Boolean openModel, RecursionLimiter limiter)

Demo fiddle #1 here.

The solution is to follow the advice in the exception message and apply the order attribute to all serializable members in the class.

Thus SchematicExport should look something like:

[XmlRoot(ElementName = "export")]
public partial class SchematicExport
{
public const string version = "D";

[XmlAttribute("version")]
[System.ComponentModel.Browsable(false), System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never), System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
public string Version { get { return version; } set { /* Do nothing */ } }

[XmlElement(Order = 1)]
public Design design { get; set; }

[XmlArray(Order = 2)]
[XmlArrayItem(typeof(Component), ElementName = "comp")]
public List<Component> components;

[XmlArray(Order = 3)]
[XmlArrayItem(typeof(LibPart), ElementName = "libpart")]
public List<LibPart> libparts;

[XmlArray(Order = 4)]
[XmlArrayItem(typeof(Library), ElementName = "library")]
public List<Library> libraries;

[XmlArray(Order = 5)]
[XmlArrayItem(typeof(Net), ElementName = "net")]
public List<Net> nets;
}

Note that setting the element order will not only reorder the elements during serialization, but also require that they be in that order during deserialization.

Incidentally, in SchematicExport.Serialize(string) you need to close your StreamWriter. The easiest way to do this is via a using statement:

public void Serialze(string filename)
{
XmlSerializer _xmlSerializer = new XmlSerializer(typeof(SchematicExport));
// FIXED ensure the file is closed.
using (var _textWriter = new StreamWriter(filename))
{
_xmlSerializer.Serialize(_textWriter, this);
}
}

The first version of this method shown in your question does not do this.

Demo fiddle #2 here.



Related Topics



Leave a reply



Submit