Search code examples
wcfdatacontractprotobuf-net

Inheritance resolved at runtime in protobuf-net


Is it possible to specify at runtime the sub-types of a specific abstract contract? In the classic WCF/DataContract we have the KnownTypeAttribute and its constructor accepting a string representing the name of static function to invoke to get a set of Type:s.

[DataContract]
[KnownType("GetTypes")]
public abstract class AbstractContract
{
    [DataMember] public int Prop1 { get; set; }
    [DataMember] public string Prop2 { get; set; }

    static IEnumerable<Type> GetTypes()
    {
        var list = new List<Type>();
        list.Add(typeof(ConcreteContract1));
        list.Add(typeof(ConcreteContract2));

        return list;
    }
}

[DataContract]
public class ConcreteContract1 : AbstractContract
{
    [DataMember] public int Prop3 { get; set; }
}

[DataContract]
public class ConcreteContract2 : AbstractContract
{
    [DataMember] public bool Prop3 { get; set; }
}

Is this scenario supported?


Solution

  • The scenario with GetTypes() isn't supported, partly due to how to v1 handles the generation/caching -however, in v2 (preview available) this is supoortable:

    using System;
    using System.Runtime.Serialization;
    using ProtoBuf.Meta;
    
    class Program
    {
        static void Main()
        {
            var model = TypeModel.Create();
            var abst = model.Add(typeof(AbstractContract), true);
            // define inheritance here...
            abst.AddSubType(10, typeof(ConcreteContract1));
            abst.AddSubType(11, typeof(ConcreteContract2));
            model.CompileInPlace();
    
            AbstractContract foo = new ConcreteContract1 { Prop1 = 123, Prop2 = "abc", Prop3 = 456 };
            AbstractContract bar = (AbstractContract)model.DeepClone(foo);
    
            Console.WriteLine(bar.Prop1);
            Console.WriteLine(bar.Prop2);
            Console.WriteLine(((ConcreteContract1)bar).Prop3);
        }
    }
    
    
    
    [DataContract]
    public abstract class AbstractContract
    {
        [DataMember(Order=1)]
        public int Prop1 { get; set; }
        [DataMember(Order=2)]
        public string Prop2 { get; set; }
    }
    
    [DataContract]
    public class ConcreteContract1 : AbstractContract
    {
        [DataMember(Order=1)]
        public int Prop3 { get; set; }
    }
    
    [DataContract]
    public class ConcreteContract2 : AbstractContract
    {
        [DataMember(Order=1)]
        public bool Prop3 { get; set; }
    }
    

    Actually, with this approach you can take away all the attributes if you want (telling it explicitly instead). Note: you should cache and re-use the compiled model as far as possible - it is thread-safe, but generating it each time will be a bit more expensive.