Search code examples
c#wcfserializationimmutabilitydatacontractserializer

DataContractSerializer and immutable types. Deserialising to a known object instance


I have a class that is effectively a object based enum. There is a static set of objects exposed by the class, and everything uses these same instances. eg (Note the private constructor)

[DataContract]
public class FieldType
{
    public static readonly FieldType Default  = new FieldType(1, "Default");
    public static readonly FieldType Name     = new FieldType(2, "Name");
    public static readonly FieldType Etc      = new FieldType(3, "Etc");

    private FieldType(uint id, string name)
    {
        Id = id;
        Name = name;
    }

    [DataMember] public uint   Id   { get; private set; }
    [DataMember] public string Name { get; private set; }
    //snip other properties
}

This works great until I have to serialise across WCF. The DataContractSerializer creates new objects by bypassing the constructor. This results in a valid FieldType object, but it is a new instance that is not one of my static instances. This makes reference comparisons against the known static values fail.

Is there any way to override the serialisation behaviour for a class so that I create the object instance instead of populating an instance supplied to me?


Solution

  • I suspect you can do:

    [DataContract]
    public class FieldType : IObjectReference
    {
        object IObjectReference.GetRealObject(StreamingContext ctx)
            switch(Id) {
                case 1: return Default;
                case 2: return Name; // note this is a collision between static/non-static
                case 3: return Etc;
                default: throw new InvalidOperationException();
            }
        }
        public static readonly FieldType Default  = new FieldType(1, "Default");
        // note this is a collision between static/non-static
        public static readonly FieldType Name     = new FieldType(2, "Name");
        public static readonly FieldType Etc      = new FieldType(3, "Etc");
    
        private FieldType(uint id, string name)
        {
            Id = id;
            Name = name; // note this is a collision between static/non-static
        }
    
        [DataMember] public uint   Id   { get; private set; }
        // note this is a collision between static/non-static
        [DataMember] public string Name { get; private set; }
        //snip other properties
    }
    

    Validated:

    public static class Program
    {
        static void Main()
        {
            var obj = FieldType.Default;
    
            using(var ms = new MemoryStream())
            {
                var ser = new DataContractSerializer(typeof (FieldType));
                ser.WriteObject(ms, obj);
                ms.Position = 0;
                var obj2 = ser.ReadObject(ms);
    
                bool pass = ReferenceEquals(obj, obj2); // true
            }
        }
    }
    

    Note, however, that there seems little point serializing the Name if we only use the Id to identify the real object to use.