shomat
shomat

Reputation: 123

Protobuf-net generic inheritance and closed constructed generic type

I have a fairly complex inheritance hierarchy including generics and we are trying to use protobuf .net for serialization purposes. Unfortunately it does not seem to be able to handle this case correctly. This is how the hierarchy looks like.

    [System.Runtime.Serialization.DataContract]
    [ProtoBuf.ProtoInclude(1000, typeof(GenericBaseClass<object>))]
    [ProtoBuf.ProtoInclude(1001, typeof(GenericBaseClass<string>))]
    public abstract class BaseClass
    {

        public int BaseProperty1 { set; get; }
        public int BaseProperty2 { set; get; }

        public BaseClass()
        {

        }

    }

    [System.Runtime.Serialization.DataContract]
    [ProtoBuf.ProtoInclude(1002, typeof(GenericDerivedClass<object>))]
    [ProtoBuf.ProtoInclude(1003, typeof(GenericDerivedClass<string>))]
    public abstract class GenericBaseClass<T> : BaseClass
    {
        /// <summary>
        /// 
        /// </summary>
        [System.Runtime.Serialization.DataMember(Order = 5)]
        public T ResponseProperty
        {
            get;
            set;
        }

        public GenericBaseClass()
        {
        }
    }

    [System.Runtime.Serialization.DataContract]
    [ProtoBuf.ProtoInclude(1004, typeof(DerivedClass1))]
    [ProtoBuf.ProtoInclude(1005, typeof(DerivedClass2))]
    public abstract class GenericDerivedClass<T> : GenericBaseClass<T>
    {
        public int AdditionalProperty { get; set; }

        public GenericDerivedClass()
        {

        }
    }

Finally these classes are implemented by two closed constructed non generic classes

    [System.Runtime.Serialization.DataContract]
    public class DerivedClass1 : GenericDerivedClass<string>             
    {
        [System.Runtime.Serialization.DataMember(Order = 6)]
        public int DerivedClass1Property { set; get; }
    }

    [System.Runtime.Serialization.DataContract]
    public class DerivedClass2 : GenericDerivedClass<object>
    {
        [System.Runtime.Serialization.DataMember(Order = 7)]
        public int DerivedClass2Property { set; get; }
    }

I have written the following test method to serialize these and it is giving me error.

    [TestMethod]
    public void SerializeDeserializeAndCompare()
    {            

        DerivedClass2 i = new DerivedClass2() { BaseProperty1 = 1, BaseProperty2 = 2, DerivedClass2Property = 3, ResponseProperty = new Object() };
        using (var file = System.IO.File.Create("test.bin"))
        {
            ProtoBuf.Serializer.Serialize(file, i);
        }

        using (var file = System.IO.File.OpenRead("test.bin"))
        {
            var o = ProtoBuf.Serializer.Deserialize<DerivedClass2>(file);
        }
    }

The error I am getting is

ProtoBuf.ProtoException: A type can only participate in one inheritance hierarchy (CapitalIQ.DataGet.UnitTests.DataSetUnitTest+DerivedClass2) ---> System.InvalidOperationException: A type can only participate in one inheritance hierarchy

Is this a limitation of protobuf .net or am I doing something incorrect. I am using r282 version.

Thanks Shobhit

Upvotes: 3

Views: 3324

Answers (1)

Marc Gravell
Marc Gravell

Reputation: 1062492

As with all attributes, the included type information on attributes apply to all closed types from the generic type definition. Thus, what you have actually defined (to protobuf-net) is:

BaseClass
: GenericBaseClass<object>
 : GenericDerivedClass<object>
  : DerivedClass1
  : DerivedClass2
 : GenericDerivedClass<string>
  : DerivedClass1
  : DerivedClass2
: GenericBaseClass<string>
 : GenericDerivedClass<object>
  : DerivedClass1
  : DerivedClass2
 : GenericDerivedClass<string>
  : DerivedClass1
  : DerivedClass2

As you can see, there are lots of duplicates - which is clearly confusing. Since attribute arguments can't use type parameters, this would leave the option of adding some kind of odd predicate mechanism, which is IMO pretty confusing. IMO, it would be a better idea to model this manually (removing the ProtoInclude attributes). I suspect your intended model is:

BaseClass
: GenericBaseClass<object>
 : GenericDerivedClass<object>
  : DerivedClass2
: GenericBaseClass<string>
 : GenericDerivedClass<string>
  : DerivedClass1

protobuf-net can work with that, but to explain the model requires "v2" and the RuntimeTypeModel:

Note also that object is a bit of a problem for protobuf; protobuf-net can fake around it with the dynamic-type option, but that is... not ideal. It certainly can't serialize an object, so for the test I've substituted a string. Note also that BaseProperty1, BaseProperty2 and AdditionalProperty are not currently marked for serialization, but can be trivially.

Anyway:

RuntimeTypeModel.Default[typeof(BaseClass)]
    .AddSubType(10, typeof(GenericBaseClass<object>))
    .AddSubType(11, typeof(GenericBaseClass<string>));

RuntimeTypeModel.Default[typeof(GenericBaseClass<object>)]
    .AddSubType(10, typeof(GenericDerivedClass<object>));
RuntimeTypeModel.Default[typeof(GenericBaseClass<object>)][5].DynamicType = true; // object!
RuntimeTypeModel.Default[typeof(GenericDerivedClass<object>)]
    .AddSubType(10, typeof(DerivedClass2));

RuntimeTypeModel.Default[typeof(GenericBaseClass<string>)]
    .AddSubType(10, typeof(GenericDerivedClass<string>));
RuntimeTypeModel.Default[typeof(GenericDerivedClass<string>)]
    .AddSubType(10, typeof(DerivedClass1));

DerivedClass2 i = new DerivedClass2() { BaseProperty1 = 1, BaseProperty2 = 2, DerivedClass2Property = 3, ResponseProperty = "some string" };
using (var file = System.IO.File.Create("test.bin"))
{
    ProtoBuf.Serializer.Serialize(file, i);
}

using (var file = System.IO.File.OpenRead("test.bin"))
{
    var o = ProtoBuf.Serializer.Deserialize<DerivedClass2>(file);
}

You don't have to use RuntimeTypeModel.Default - in fact, I recommend using (and caching) a separate type-model; but Serializer.Serialize points at the default model. If you create a custom model (TypeModel.Create) just store it somewhere and use the Serialize etc from there.

Upvotes: 5

Related Questions