Tuesday, July 30, 2013

Using protobuf-net with Inheritance

I have been doing some work with Google's Protocol Buffers using Marc Gravel's protobuf-net. It is a very easy to use package and has proven to be lightning fast in both serializing and deserializing my objects. As much as I have enjoyed it, this post is not a sales pitch about the goodness of protocol buffers but is, instead, to show an issue I ran across and how I got around it.

I was having a problem with inheritance and deserializing my objects. Basically, I have a value object generic that I use for identities and they were not deserializing properly.

[ProtoContract]
public abstract class Identity<TId> : IIdentity
    where TId : Identity<TId>
{
    [ProtoMember(1)]
    public string Value { get; private set; }

    protected Identity(string value)
    {
        Value = value;
    }
}

When the identity object would be deserialized, the Value property was always null. So, after some reading, I found out why. It was because I had to let protobuf-net know about my inheritance chain. One way of doing it is with attributes. (Specifically the ProtoInheritanceAttribute.)

[ProtoContract]
public class FooId : Identity<FooId>
{
    private FooId() { /* Just for serialization */ }
    public FooId(string value)
        : base(value) { }
}

[ProtoContract]
[ProtoInheritance(2, typeof(FooId))]
public abstract class Identity<TId> : IIdentity
    where TId : Identity<TId>
{
    [ProtoMember(1)]
    public string Value { get; private set; }

    protected Identity(string value)
    {
        Value = value;
    }
    protected Identity() { /* Just for serialization */ }
}

For one, it through me off that the inheritance attribute goes on the base class. DataContractSerializer and XmlSerializer come with the same issue with attributes and inheritance so I should have seen it coming. For one-off inheritance chains, this really isn't too bad of a trade-off especially for what you get. For me, though, I would have had to append an uncountable number of ProtoInheritanceAttributes on my Identity class. It was not going to work. Luckily, there is a little trick to make the serialization work the way I wanted it to. Since this is an abstract class, we can make the exposed property abstract and move up the ProtoMember attribute to the child class. (Be sure to note the change to from private to protected on the Value property.)

[ProtoContract]
public class FooId : Identity<FooId>
{
    [ProtoMember(1)]
    public override string Value { get; protected set; }

    private FooId() { /* Just for serialization */ }
    public FooId(string value)
        : base(value) { }
}

public abstract class Identity<TId> : IIdentity
    where TId : Identity<TId>
{
    public abstract string Value { get; protected set; }

    protected Identity(string value)
    {
        Value = value;
    }
    protected Identity() { /* Just for serialization */ }
}

It also cleans up the base class and rids it of the attributes.

No comments:

Post a Comment