DataContractSerializer and immutable types. Deserialising to a known object instance
Asked Answered
B

2

5

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?

Brunella answered 11/4, 2012 at 11:1 Comment(2)
(note Name is used for static and non-static, meaning: it won't compile as-is)Hakon
Oops, yes. I sanitized for the question and picked a bad name.Brunella
H
8

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.

Hakon answered 11/4, 2012 at 11:6 Comment(1)
This looks perfect, thanks! (Yes, the name isn't needed. Was still in there from my naive bog standard WCF implementation)Brunella
T
5

I suggest you override Equals and GetHashcode (and == and !=) so that your equality check of the static object with the one created by WCF works.

Data Transfer Objects (DTOs) are not meant for Object Oriented behaviour and they are purely state classes. But I can understand the problem you are facing.

Alternatively use a different DTO for sending the data across while your domain objects work with the class above.

Tingley answered 11/4, 2012 at 11:6 Comment(2)
Also, the == / != operators would be useful, if it represents an object-enumHakon
Thanks. Yes, I may well end up taking this route. I was just trying to keep it to reference comparisons for simplicity (and because I'm lazy).Brunella

© 2022 - 2024 — McMap. All rights reserved.