Constructors Not Called On Deserialization
Asked Answered
J

3

2

Various places I've been reading have pointed out that on deserialization, the .NET Framework makes a call to FormatterServices.GetUninitializedObject, in which constructors are not called and field initializers are not set. If this is true, why is my constructor being called? Are there instances where constructors and field initializers could be called?

My Class:

[DataContract]
public class TestClass
{
    [DataMember]
    public string Val1 { get; set; }

    [DataMember]
    public string Val2 { get; set; }

    [DataMember]
    public bool NonDefaultBool = true;

    private int _nonDefaultInt = 1234;

    [DataMember]
    public int NonDefaultInt
    {
        get { return _nonDefaultInt; }
        set { _nonDefaultInt = value; }
    }

    public TestClass()
    {
        Val1 = "hello";
    }
}

My de/serialization code:

var json2 =
@"{
    ""Val1"":""hello""
}";

using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(json2)))
{
    var thing = DeserializeJsonObject<TestClass>(ms);
    Console.WriteLine(GetSerializedData(thing));
}

// ... code left out

protected static TModel DeserializeJsonObject<TModel>(Stream data) where TModel : class
{
    DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(TModel));
    return jsonSerializer.ReadObject(data) as TModel;
}

static string GetSerializedData<T>(T data)
{
    DataContractJsonSerializer jsonSerializer = new DataContractJsonSerializer(typeof(T), _knownTypes);

    using (MemoryStream ms = new MemoryStream())
    {
        jsonSerializer.WriteObject(ms, data);
        return Encoding.UTF8.GetString(ms.ToArray());
    }
}

My output (formatted and commented me):

{
    "NonDefaultBool":false, // field initializer not set
    "NonDefaultInt":0, // field initializer not set
    "Val1":"hello", // constructor called
    "Val2":null
}
Jeweller answered 16/2, 2011 at 20:55 Comment(1)
Could you show the GetSerializedData code?Malliemallin
S
3

You're deserializing the json2 string.

var json2 =
@"{
""Val1"":""hello""
}";

I don't believe the constructor is being called, but the 'hello' is being assigned by the JSON string.

Selena answered 16/2, 2011 at 21:25 Comment(1)
Ugh. Yea, the constructor is supposed to be assigning to Val2. This question did come up, though, because I have another instance where the field initializers appear to be being called. But that's a separate question at this point.Jeweller
B
3

In case it might be useful for someone else. The answer is providing [OnDeserializing] handler on your data contract. In your case the implementation would look like:

[DataContract]
public class TestClass
{
    [DataMember]
    public string Val1 { get; set; }

    [DataMember]
    public string Val2 { get; set; }

    [DataMember]
    public bool NonDefaultBool;

    [DataMember]
    public int NonDefaultInt { get; set; }

    private void InitializeDefaults()
    {
        Val1 = "hello";
        NonDefaultBool = true;
        NonDefaultInt = 1234;
    }

    #region Construction

    public TestClass()
    {
        InitializeDefaults();
    }

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

    #endregion
}
Baillieu answered 9/11, 2014 at 21:1 Comment(0)
W
1

OK, so after complaining about it again, I've sort-of come up with a solution, if you're willing to inherit a base class, and not too bothered about using Reflection:

[DataContract]
class ConstructedDataContract
{
    [OnDeserializing]
    void OnDeserializing(StreamingContext context)
    {
        ConstructorInfo ci = this.GetType().GetConstructor(new Type[] { });
        if (ci != null)
        {
            ci.Invoke(this, new object[] { });
        }
    }
}

Then just inherit that base class

[DataContract]
class MyClass1 : ConstructedDataContract
{
    [DataMember(IsRequired=false)]
    public int Var1 = 5; // This will initialise to 5, and if the field is
                         // included in the serialisation stream, then it 
                         // will be overwritten.
}

OnDeserializing will be called on the base class, which will use reflection to run the classes default constructor. In the case above, the default constructor sets Var1 to 5, even though there is no explicit constructor block. If there were, then code from that block would be executed too.

Weak answered 24/6, 2016 at 14:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.