Field Initializer in C# Class not Run when Deserializing
Asked Answered
D

2

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;
    }
}
Dank answered 23/2, 2012 at 19:20 Comment(2)
Tried adding a protected constructor in the abstract where you initialize the dict? Provide a public constructor in MyConcrete that chains to : base().Kktp
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... #5022473Dank
D
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; }
    }
}
Deflower answered 23/2, 2012 at 19:35 Comment(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.Dank
Oh yeah... You're right about the readonly. Woops, missed that one!Deflower
@Deflower +1 Thanks for this solution, I can live without readonlyArterialize
Read-only fields are still possible with reflection: https://mcmap.net/q/501788/-initialize-private-readonly-fields-after-deserializingHenandchickens
@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.Dank
A
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.

Apace answered 5/9, 2012 at 8:58 Comment(3)
+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.Dank
That will bring lazyness to you class - for readonly properties it is cool.Constantia
Why not remove the field initializer altogether? No reason to create the dictionary until you need it.Connatural

© 2022 - 2024 — McMap. All rights reserved.