10

DataContractSerializer does not call a constructor or invoke field initializers when deserializing:

DataContractSerializer doesn't call my constructor?

Field Initializer in C# Class not Run when Deserializing

Setting the initial value of a property when using DataContractSerializer

Is it possible to initialize a readonly field after object deserialization? Must I abandon that language feature in order to use DataContractSerializer?

Community
  • 1
  • 1
Eric J.
  • 147,927
  • 63
  • 340
  • 553
  • @svick: I cannot think of any mechanism to perform the initalization. DataContractSerializer uses `FormatterServices.GetUninitializedObject` to "construct" an empty object and does not invoke a constructor or run field initializers. As far as I understand, a `readonly` field can only be initialized in one of those two ways (but hopefully there's another way I'm not familiar with). – Eric J. Feb 24 '12 at 01:26
  • Oh, I thought you wanted to deserialize the field. Do I understand you correctly that you for example want to set the field to some default value, that doesn't come from the serialized data? – svick Feb 24 '12 at 01:30
  • @svick: Yes, in my case I have an object whose constructor generates internal state and stores it in a `readonly` field. I can certainly use the `OnDeserialized` attribute to initialize object state the way `DataContractSerializer` wants me to, but then I see no way to also use the `readonly` attribute to protect that state from post-initialization modification. – Eric J. Feb 24 '12 at 01:35

3 Answers3

4

I'm not sure doing this is a good idea, but you can change the value of a readonly field outside the constructor or field initializer by using reflection.

Putting something like:

typeof(MyType).GetField("Field").SetValue(this, value);

in your deserialization callback should work.

svick
  • 236,525
  • 50
  • 385
  • 514
  • I had both thoughts as well... reflection will probably work, but it's probably not a great idea. Still, perhaps it is the only option. – Eric J. Feb 24 '12 at 01:39
3

Yes, using DataContractSerializer you can serialize a readonly field. You can even serialize a non-public readonly field.

using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization;

namespace ConsoleApplication30
{
    class Program
    {
        static void Main(string[] args)
        {
            Test a = new Test(1, 2);
            Test b;
            using (var ms = new MemoryStream())
            {
                DataContractSerializer ser = new DataContractSerializer(typeof(Test));
                ser.WriteObject(ms, a);
                ms.Position = 0;
                b = (Test) ser.ReadObject(ms);
            }
            Trace.Assert(a.Data1 == b.Data1);
            Trace.Assert(a.Data2 == b.Data2);
        }
    }

    [DataContract]
    public class Test
    {
        [DataMember]
        public readonly int Data1;

        [DataMember]
        private readonly int _data2;
        public int Data2
        {
            get { return _data2; }   
        }

        public Test(int data1, int data2)
        {
            Data1 = data1;
            _data2 = data2;
        }
    }
}
Jason Kresowaty
  • 16,105
  • 9
  • 57
  • 84
  • 1
    The problem is that the object (which was designed in ignorance of the behavior of specific serializers) initializes object state in the constructor, and writes that state into a `readonly` field. The contents of the `readonly` field themselves are not intended to be serialized. There is no reason to serialize that internal state because it can always be re-created, and conceptually is private to the implementation of the class. – Eric J. Feb 24 '12 at 01:37
  • 1
    @EricJ.Serialization is [by definition](https://en.wikipedia.org/wiki/Serialization) a process of translating data structures or object state. That's why _it is object fields that are intended to be serialized_, not properties (their values are always recreated from fields) or constructor parameters (the are not necessarily captured by object state). If one needs to recreate object from a set of constructor parameters, this set of constructor parameters should be serialized instead, not object itself. – Lightman Aug 03 '16 at 09:45
0

I found a clean way to achieve what you are looking for without breaking your design.

Using this method will ensure that your constructor will be called and your readonly field properly set.


What you need is to actually serialize and deserialize [DataMember] marked fields from a DataModel class.

That will prevent any unintended behavior to happen knowing that DataContractSerializer does not call the constructor when deserializing.

namespace MyApp.DataModel
{
    //It is not mandatory to specify the DataContract since a default one will be
    //provided on recent version of .Net, however it is a good practice to do so.
    [DataContract(Name = "MyClassDataModel", Namespace = "MyApp.DataModel")]
    public class MyClassDataModel
    {
        [DataMember]
        public bool DataMemberExample{ get; set; }
    }

}

For deserializing and serializing you can now use this class to hold your values.

Upon loading you can create a new data model object and pass it to your class which needs to have its constructor called.

public MyObjectThatNeedToBeConstructed LoadData(){
    // ... 
    // get your reader (Stream) from the file system or wherever it's located
    var deserializer = new DataContractSerializer(typeof(MyClassDataModel));
    var storedModel = (MyClassDataModel)deserializer.ReadObject(reader);

    return new MyObjectThatNeedToBeConstructed(storedModel);
}

Upon saving you can extract the required data from your class that contain the ReadOnly field.

public void SaveData(MyObjectThatNeedToBeConstructed myObject){
    // ... 
    // get your writer (Stream) from memory or wherever it's located
    var serializer = new DataContractSerializer(typeof(MyClassDataModel));
    var dataModel = new MyClassDataModel{ DataMemberExample = myObject.DataMember};
    serializer.WriteObject(writer, dataModel);
}

Of course, you will have to add an overload to the constructor and you might have to tweak the example a bit, but I think you got the picture.

ForceMagic
  • 6,230
  • 12
  • 66
  • 88