Общее наследование Protobuf-net и закрытый сконструированный универсальный тип

У меня довольно сложная иерархия наследования, включая дженерики, и мы пытаемся использовать protobuf .net для целей сериализации. К сожалению, похоже, он не может правильно обработать этот случай. Вот так выглядит иерархия.

    [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()
        {

        }
    }

Наконец, эти классы реализованы двумя закрытыми сконструированными неуниверсальными классами.

    [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; }
    }

Я написал следующий тестовый метод для их сериализации, и он дает мне ошибку.

    [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);
        }
    }

Ошибка, которую я получаю,

ProtoBuf.ProtoException: Тип может участвовать только в одной иерархии наследования (CapitalIQ.DataGet.UnitTests.DataSetUnitTest+DerivedClass2) ---> System.InvalidOperationException: Тип может участвовать только в одной иерархии наследования

Это ограничение protobuf.net или я что-то не так делаю. Я использую версию r282.

Спасибо Шобхит


person shomat    schedule 28.06.2011    source источник
comment
Что-то вроде этого должно работать. Я сейчас на дежурстве с детьми, но посмотрю позже   -  person Marc Gravell    schedule 28.06.2011


Ответы (1)


Как и для всех атрибутов, включенная в атрибуты информация о типе применяется ко всем закрытым типам из определения универсального типа. Таким образом, то, что вы фактически определили (для protobuf-net):

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

Как видите, дубликатов много, что явно сбивает с толку. Поскольку аргументы атрибутов не могут использовать параметры типа, это оставило бы возможность добавить какой-то странный механизм предиката, который, по-моему, довольно сбивает с толку. ИМО, было бы лучше смоделировать это вручную (удалив атрибуты ProtoInclude). Я подозреваю, что ваша предполагаемая модель:

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

protobuf-net может с этим работать, но для объяснения модели требуется "v2" и RuntimeTypeModel:

Обратите также внимание, что object представляет собой небольшую проблему для protobuf; protobuf-net может обманывать его с опцией динамического типа, но это... не идеально. Он, конечно, не может сериализовать object, поэтому для теста я заменил строку. Также обратите внимание, что BaseProperty1, BaseProperty2 и AdditionalProperty в настоящее время не помечены для сериализации, но могут быть тривиально.

Так или иначе:

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);
}

Вам не обязательно использовать RuntimeTypeModel.Default — на самом деле, я рекомендую использовать (и кэшировать) отдельную модель типов; но Serializer.Serialize указывает на модель по умолчанию. Если вы создаете пользовательскую модель (TypeModel.Create), просто сохраните ее где-нибудь и используйте оттуда Serialize и т. д.

person Marc Gravell    schedule 28.06.2011
comment
Большое спасибо за ваш быстрый ответ!! Учитывая, что пример был всего лишь ветвью более сложной иерархии классов, ручное создание typeModel может оказаться очень сложным. А еще мой плохой, я просто использовал объект в качестве параметра типа в качестве примера. Вместо этого у нас есть другой объект, заполняющий параметр T. Я попробую и посмотрю, смогу ли я автоматизировать использование отражения. Но в противном случае я думаю, что должен быть какой-то способ, которым мы могли бы представлять такие иерархии, используя атрибуты. - person shomat; 29.06.2011
comment
Я столкнулся с этим исключением после копирования неправильного атрибута KnownType, который был дочерним элементом дочернего класса. Было бы полезно, если бы текст исключения содержал повторяющийся класс, если это возможно. - person Shaun; 27.11.2013