29

I have a class that defines a protected field. The protected field has a field initializer.

When I deserialize the concrete class, the field initializer is not run. Why? What is the best pattern to solve the problem? If I move the initialization into a constructor, the constructor is also not invoked.

[DataContract]
public class MyConcrete
{
    // FIELD INITIALIZER DOES NOT RUN WHEN COMMENTED IN:
    protected readonly Dictionary<int, string> myDict;// = new Dictionary<int, string>();

    public MyConcrete()
    {
        myDict = new Dictionary<int, string>();
    }

    private bool MyMethod(int key)
    {
        return myDict.ContainsKey(key);
    }

    private int myProp;

    [DataMember]
    public int MyProp
    {
        get { return myProp; }
        set { bool b = MyMethod(value); myProp = value; } // Call MyMethod to provoke error
    }
}

ORIGINAL CLASS HIERARCHY

[DataContract]
public abstract class MyAbstract
{
    // THIS INITIALIZER IS NOT RUN WHILE DESERIALIZING:
    protected readonly Dictionary<int, string> myDict = new Dictionary<int, string>();

    private bool MyMethod(int key)
    {
        return myDict.ContainsKey(key);
    }

    private int myProp;

    [DataMember]
    public int MyProp
    {
        get { return myProp; }
        set { bool b = MyMethod(value); myProp = value; } // Call MyMethod to provoke error
    }
}

[DataContract]
public class MyConcrete : MyAbstract
{

}

class Program
{
    static void Main(string[] args)
    {
        string tempfn = Path.GetTempFileName();

        MyConcrete concrete = new MyConcrete() { MyProp = 42 };
        string data = concrete.SerializeToString<MyConcrete>();

        MyConcrete rehydrated = SerializationHelper.DeserializeFromString<MyConcrete>(data);
    }
}

SUPPORTING METHODS

static public string SerializeToString<T>(this T obj)
{
    return SerializationHelper.SerializeToString<T>(obj);
}

static public string SerializeToString<T>(T obj)
{
    DataContractSerializer s = new DataContractSerializer(typeof(T));
    using (MemoryStream ms = new MemoryStream())
    {
        s.WriteObject(ms, obj);
        ms.Position = 0;
        using (StreamReader sr = new StreamReader(ms))
        {
            string serialized = sr.ReadToEnd();
            return serialized;
        }
    }            
}

static public T DeserializeFromString<T>(string serializedDataAsString)
{
    DataContractSerializer s = new DataContractSerializer(typeof(T));
    using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(serializedDataAsString)))
    {
        object s2 = s.ReadObject(ms);
        return (T)s2;
    }
}
Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • 1
    Tried adding a protected constructor in the abstract where you initialize the dict? Provide a public constructor in MyConcrete that chains to `: base()`. –  Feb 23 '12 at 19:27
  • I flattened this into just one class and added a constructor. The constructor is not being invoked. Found this related post that claims field initializers and constructors are not invoked... http://stackoverflow.com/questions/5021973/constructors-not-called-on-deserialization – Eric J. Feb 23 '12 at 19:34

2 Answers2

47

On deserialization neither the constructors nor the field initializers are called and a "blank" un-initialized object is used instead.

To resolve it you can make use of the OnDeserializing or OnDerserialized attributes to have the deserializer call a function with the following signature:

void OnDeserializing(System.Runtime.Serialization.StreamingContext c);

In that function is where you can initialize whatever was missed within the deserialization process.

In terms of convention, I tend to have my constructor call a method OnCreated() and then also have deserializating method call the same thing. You can then handle all of the field initialization in there and be sure it's fired before deserialization.

[DataContract]
public abstract class MyAbstract
{
    protected Dictionary<int, string> myDict;

    protected MyAbstract()
    {
        OnCreated();
    }

    private void OnCreated()
    {
        myDict = new Dictionary<int, string>();
    }

    [OnDeserializing]
    private void OnDeserializing(StreamingContext c)
    {
        OnCreated();
    }

    private bool MyMethod(int key)
    {
        return myDict.ContainsKey(key);
    }

    private int myProp;

    [DataMember]
    public int MyProp
    {
        get { return myProp; }
        set { bool b = MyMethod(value); myProp = value; }
    }
}
Onur Demir
  • 708
  • 2
  • 13
  • 35
Reddog
  • 15,219
  • 3
  • 51
  • 63
  • 5
    I cannot assign a readonly field from the OnDeserializing() method... this design choice for the serializer breaks the ability to use readonly... Still, seems to be the best/only approach. – Eric J. Feb 23 '12 at 19:51
  • Oh yeah... You're right about the readonly. Woops, missed that one! – Reddog Feb 23 '12 at 22:57
  • @Reddog +1 Thanks for this solution, I can live without readonly – surfen Apr 02 '12 at 18:49
  • 1
    Read-only fields are still possible with reflection: http://stackoverflow.com/a/12398681/135138 – Tim Sylvester Sep 13 '12 at 02:38
  • 3
    @TimSylvester: Almost everything is possible via reflection :-) Sad that the language designers would force an object designer to use reflection to ensure an object is always properly initialized. – Eric J. Feb 06 '13 at 15:49
9

Another approach is to access your field through a protected (in your example) property, and initialise the field using the null-coalescing (??) operator

protected Dictionary<int, string> myDict = new Dictionary<int, string>(); 

protected Dictionary<int, string> MyDict
{
    get
    {
        return myDict ?? (myDict = new Dictionary<int, string>());
    }
}

The downsides are that you lose the benefits of readonly, and need to make sure that you only access the value via the property.

Richard Ev
  • 52,939
  • 59
  • 191
  • 278
  • +1, provides semantics similar to *readonly* as long as all developers that work on the class understand the convention of accessing `MyDict` instead of `myDict`. Somewhere that will cause a bug to happen, but an option appropriate to some circumstances. – Eric J. Feb 06 '13 at 15:53
  • 1
    That will bring lazyness to you class - for readonly properties it is cool. – EvgeniyK Sep 28 '13 at 08:04
  • 1
    Why not remove the field initializer altogether? No reason to create the dictionary until you need it. – Michael Richardson Apr 28 '14 at 16:37