Udostępnij za pośrednictwem


Warning: DataContractSerializer won’t call your constructor!

Consider the following naïve data contract:

 [DataContract]
public class Data
{
    private int[] array;

    public Data()
    {
        this.array = new int[13];
    }

    public int Length { get { return this.array.Length; } }
}

It looks ok, right? Let’s use it then:

 DataContractSerializer serializer = new DataContractSerializer(typeof(Data));
Data data = new Data();

using (MemoryStream stream = new MemoryStream())
{
    serializer.WriteObject(stream, data);
    stream.Position = 0; // Rewind

    data = (Data)serializer.ReadObject(stream);

    Console.WriteLine(data.Length); // throws a NullReferenceException
}

Why was this.array not initialized? Well, I gave the answer away in the title; the constructor was never called. It turns out that DataContractSerializer (and BinaryFormatter, by the way), unlike XmlSerializer, creates an uninitialized (without calling any constructors) instance of the type it’s de-serializing. Then data members are de-serialized.

In the example above, a very simple change will fix the issue. If you’re running .Net 3.5 SP1 or later (and you should be), the DataContractSerializer can serialize types without the [DataContract] attribute, i.e. Plain Old CLR Object (POCO) types, as long as the type is public and has a parameter-less constructor (regardless of its visibility) – just like XmlSerializer. And yes, you guessed it, in this case, the constructor will be called.

What if you don’t want to define a parameter-less constructor, or if this isn’t a viable option? You should use one of the serialization callbacks listed in the following article: https://msdn.microsoft.com/en-us/library/ms733734(v=vs.110).aspx

Here’s an example:

 [DataContract]
public class Data
{
    private int[] array;

    public Data()
    {
        Initialize();
    }

    private void Initialize()
    {
        this.array = new int[13];
    }

    [OnDeserializing]
    private void SetValuesOnDeserializing(StreamingContext context)
    {
        Initialize();
    }

    public int Length { get { return this.array.Length; } }
}