Object construction and the DataContractSerializer

January 23, 2010

The other day I wrote a bit about the issue with read-only collection properties and XmlSerializer . Today I will write about a similar issue, but this time it is the relation between read-only collections and the DataContractSerializer. The symptom was similar, but the cause and the solution entirely different. Well, it's not strictly the relation between read-only collections and the DataContractSerializer, but that was the scenario that led me to stumble upon the behavior that led to this blog post.

Let's start with the Manager sample that the previous post resulted in (here decorated with attibutes to allow it to be exposed through a WCF service):

[DataContract]
public class Manager : Employee
{
    private List<Employee> _subordinates;;

    public Manager()
    {
        _subordinates = new List<Employee>();
    }

    [DataMember]
    public List<Employee> Subordinates
    {
        get
        {
            return _subordinates;
        }
    }
}

While the above class serializes and deserializes without any issues, passing an instance of this class in a WCF service call generates a FaultException with a message that I found a bit puzzling:

The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:manager. The InnerException message was 'The get-only collection of type 'System.Collections.Generic.List`1[[WcfService1.Employee, WcfService1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]' returned a null value. The input stream contains collection items which cannot be added if the instance is null. Consider initializing the collection either in the constructor of the the object or in the getter.'. Please see InnerException for more details.

I found the advice in the end particularly interesting: Consider initializing the collection either in the constructor of the the object or in the getter.. Isn't that exactly what the constructor does? This advice is also interesting given that the following can be found in the DataContractSerializer documentation:

When instantiating the target object during deserialization, the DataContractSerializer does not call the constructor of the target object.

...and...

If you code the class that assumes the constructor has run, the class may get into an invalid internal state that is exploitable.

Clearly, the constructor is not the way to go on this one. Enter OnDeserializing attribute. This attribute can be used to decorate a method that will be executed by the serializer during deserialization. This mechanism can be used to initialize fields when the constructor is not run. Time to refactor the class again:

[DataContract]
public class Manager : Employee
{

    private List<Employee> _subordinates;

    public Manager()
    {
        Initialize();
    }

    [OnDeserializing]
    private void OnDeserializing(StreamingContext context)
    {
        Initialize();
    }
    
    private void Initialize()
    {
        _subordinates = new List<Employee>();
    }
    
    [DataMember]
    public List<Employee> Subordinates
    {
        get
        {
            return _subordinates;
        }
    }
}
 

Now, the collection will be initialized regardless of whether a Manager object is constructed in a normal manner, or if it is created during serialization in a manner that bypasses the constructor.