具有继承的私有字段的Protobuf-net RuntimeTypeModel失败,基类上的ProtoInclude属性起作用

时间:2019-06-01 16:04:40

标签: protobuf-net

如果我的测试设置为

    [ProtoContract]
    [ProtoInclude(1, typeof(DecoratorDerived))]
    public class DecoratorBase
    {
        [ProtoMember(2)]
        private int baseValue1;

        private int baseValue2;

        public DecoratorBase()
        {
            baseValue1 = (new Random()).Next();
            baseValue2 = (new Random()).Next();
        }

        protected void ShowBaseValue()
        {
            Console.WriteLine($"DecoratorBase - baseValue1: {baseValue1}, baseValue2: {baseValue2}");
        }
    }

    [ProtoContract]
    public class DecoratorDerived : DecoratorBase
    {
        [ProtoMember(2)]
        public int derivedValue1;

        private int derivedValue2;

        public DecoratorDerived()
        {
            derivedValue1 = (new Random()).Next();
            derivedValue2 = (new Random()).Next();
        }

        public void ShowValues()
        {
            ShowBaseValue();
            Console.WriteLine($"DecoratorDerived - derivedValue1: {derivedValue1}, derivedValue2: {derivedValue2}");
        }
    }

        static void DecoratorTest()
        {
            var c1 = new DecoratorDerived();
            c1.ShowValues();

            byte[] raw;
            using (var stream = new MemoryStream())
            {
                ProtoBuf.Serializer.Serialize<DecoratorDerived>(stream, c1);
                raw = stream.ToArray();
            }

            DecoratorDerived c2;
            using (var stream = new MemoryStream(raw))
            {
                c2 = ProtoBuf.Serializer.Deserialize<DecoratorDerived>(stream);
            }
            c2.ShowValues();
        }

一切正常,但实际问题是我的基础类是通过T4自动生成的,并且它们很多,因此添加所有ProtoInclude行是不可行的。进行的一些研究表明,使用RuntimeTypeModel可以即时执行操作。因此将测试更改为

    [ProtoContract]
    public class RTMBase
    {
        [ProtoMember(2)]
        private int baseValue1;

        private int baseValue2;

        public RTMBase()
        {
            baseValue1 = (new Random()).Next();
            baseValue2 = (new Random()).Next();
        }

        protected void ShowBaseValue()
        {
            Console.WriteLine($"RTMBase - baseValue1: {baseValue1}, baseValue2: {baseValue2}");
        }
    }

    [ProtoContract]
    public class RTMDerived : RTMBase
    {
        [ProtoMember(2)]
        public int derivedValue1;

        private int derivedValue2;

        public RTMDerived()
        {
            derivedValue1 = (new Random()).Next();
            derivedValue2 = (new Random()).Next();
        }

        public void ShowValues()
        {
            ShowBaseValue();
            Console.WriteLine($"RTMDerived - derivedValue1: {derivedValue1}, derivedValue2: {derivedValue2}");
        }
    }

        static void RTMTest(RuntimeTypeModel runtimeTypeModel)
        {
            var c1 = new RTMDerived();
            c1.ShowValues();

            // setup RTM, https://stackoverflow.com/questions/40608767/inheritance-in-protobuf-net-adding-a-lower-base-class-still-backward-compatible
            var myType = runtimeTypeModel[typeof(RTMDerived)];
            var baseType = runtimeTypeModel[typeof(RTMDerived).BaseType];
            if (!baseType.GetSubtypes().Any(s => s.DerivedType == myType))
            {
                foreach (var field in baseType.GetFields())
                {
                    myType.Add(field.FieldNumber + 500, field.Name);
                }
            }

            byte[] raw;
            using(var stream = new MemoryStream())
            {
                ProtoBuf.Serializer.Serialize<RTMDerived>(stream, c1);
                raw = stream.ToArray();
            }

            RTMDerived c2;
            using(var stream = new MemoryStream(raw))
            {
                c2 = ProtoBuf.Serializer.Deserialize<RTMDerived>(stream);
            }
            c2.ShowValues();
        }

我有例外

RTMBase - baseValue1: 1874947795, baseValue2: 1391655165
RTMDerived - derivedValue1: 922997568, derivedValue2: 837049520

Unhandled Exception: System.ArgumentException: Unable to determine member: baseValue1
Parameter name: memberName
   at ProtoBuf.Meta.MetaType.AddField(Int32 fieldNumber, String memberName, Type itemType, Type defaultType, Object defaultValue) in C:\code\protobuf-net\src\protobuf-net\Meta\MetaType.cs:line 1437
   at ProtoBuf.Meta.MetaType.Add(Int32 fieldNumber, String memberName) in C:\code\protobuf-net\src\protobuf-net\Meta\MetaType.cs:line 1261
   at proto_error.Program.RTMTest1(RuntimeTypeModel runtimeTypeModel) in /Users/christian/tmp/proto-error/Program.cs:line 52
   at proto_error.Program.Main(String[] args) in /Users/christian/tmp/proto-error/Program.cs:line 17

对我来说幸运的是,将RTMBase.baseValue1更改为public,protected或internal使其可以工作,而internal对于我的用例来说就很好。但是我很好奇这是一个错误还是做错了什么?

添加

如果我将RMTest更改为

        static void RTMTest2(RuntimeTypeModel runtimeTypeModel)
        {
            var c1 = new RTMDerived();
            c1.ShowValues();

            // setup RTM, https://stackoverflow.com/questions/10400539/protobuf-net-runtimetypemodel-not-serializing-members-of-base-class
            var baseType = runtimeTypeModel[typeof(RTMDerived).BaseType];
            baseType.AddSubType(500, typeof(RTMDerived));

            byte[] raw;
            using (var stream = new MemoryStream())
            {
                ProtoBuf.Serializer.Serialize<RTMDerived>(stream, c1);
                raw = stream.ToArray();
            }

            RTMDerived c2;
            using (var stream = new MemoryStream(raw))
            {
                c2 = ProtoBuf.Serializer.Deserialize<RTMDerived>(stream);
            }
            c2.ShowValues();
        }

它工作得很好,但是我仍然很好奇RTMRest1为何失败。另外,每种类型的500参数是否需要不同,还是可以为常数?

1 个答案:

答案 0 :(得分:0)

提供的密钥用于每次唯一地标识为父邮件中的一个字段,所以是的:每个类型的密钥需要且可靠-例如,如果现在为500,则为在两年内重新加载数据时,也需要为500。

这也适用于字段编号,因此此处显示的代码非常危险:

foreach (var field in baseType.GetFields())
{
    myType.Add(field.FieldNumber + 500, field.Name);
}
  1. reflect不能保证它返回事物的任何 order ,会破坏数据
  2. 如果有人添加了额外的字段,则可以更改其他字段的字段编号,从而破坏数据

至于找不到成员,我想它是在寻找它的声明成员。尽管可能会造成歧义和脆弱的情况,但可能会改变它们。以下代码是完全有效的,并且可以正常编译:

class Foo
{
    private int a;
}
class Bar : Foo
{
    private int a;
}

但是"a"的含义会根据我们在考虑Foo还是Bar而变化。对于您的情况(将基本成员解析为派生类型),您想到的是Foo.a,但您可能会要求它从Bar开始,因此要解决的逻辑问题是{ {1}}。我认为(从内存中),这就是为什么我们将事情限制为声明的成员。