Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is of <T> allowed in a run-time ProtoBuf-net model?

I am using version 2 of ProtoBuf-net, and currently I'm geting the error "Unable to determine member: A"

Is it possible to create a run-time model for Protobuf-net when we use ClassOfType<T>? If so, can anyone spot what I'm missing in the below code?

btw: this request is modelled off of Deserialize unknown type with protobuf-net I could get a version of this going just fine... but they are using an abstract base class, not a generic class of T.

THIS IS A WORKING EXAMPLE (stuff that wasn't working is removed).

using System;
using System.IO;
using NUnit.Framework;
using ProtoBuf;
using ProtoBuf.Meta;

namespace ProtoBufTestA2
{
    [TestFixture]
    public class Tester
    {
        [Test]
        public void TestMsgBaseCreateModel()
        {
            var BM_SD = new Container<SomeDerived>();

            using (var o = BM_SD) {
                o.prop1 = 42;
                o.payload = new SomeDerived();
                using (var d = o.payload) {
                    d.SomeBaseProp = -42;
                    d.SomeDerivedProp = 62;
                }
            }

            var BM_SB = new Container<SomeBase>();
            using (var o = BM_SB) {
                o.prop1 = 42;
                o.payload = new SomeBase();
                using (var d = o.payload) {
                    d.SomeBaseProp = 84;
                }
            }
            var model = TypeModel.Create();

            model.Add(typeof(Container<SomeDerived>), true);  // BM_SD
            model.Add(typeof(Container<SomeBase>), true);  // BM_SB
            model.Add(typeof(SomeBase), true); // SB
            model.Add(typeof(SomeDerived), true);  // SD
            model[typeof(SomeBase)].AddSubType(50, typeof(SomeDerived)); // SD

            var ms = new MemoryStream();

            model.SerializeWithLengthPrefix(ms, BM_SD, BM_SD.GetType(), ProtoBuf.PrefixStyle.Base128, 0);

            model.SerializeWithLengthPrefix(ms, BM_SB, BM_SB.GetType(), ProtoBuf.PrefixStyle.Base128, 0);
            ms.Position = 0;
            var o1 = (Container<SomeDerived>)model.DeserializeWithLengthPrefix(
                ms
                , null
                , typeof(Container<SomeDerived>), PrefixStyle.Base128, 0);
            var o2 = (Container<SomeBase>)model.DeserializeWithLengthPrefix(
                ms
                , null
                , typeof(Container<SomeBase>), PrefixStyle.Base128, 0);
        }
    }

    [ProtoContract]
    public class Container<T> : IDisposable
    {
        [ProtoMember(1)]
        public int prop1 { get; set; }

        [ProtoMember(2)]
        public T payload { get; set; }

        public void Dispose() { }
    }

    [ProtoContract]
    public class AnotherDerived : SomeDerived, IDisposable
    {
        [ProtoMember(1)]
        public int AnotherDerivedProp { get; set; }
        public override void Dispose() { }
    }

    [ProtoContract]
    public class SomeDerived : SomeBase, IDisposable
    {
        [ProtoMember(1)]
        public int SomeDerivedProp { get; set; }

        public override void Dispose() { }
    }

    [ProtoContract]
    public class SomeBase : IDisposable
    {
        [ProtoMember(1)]
        public int SomeBaseProp { get; set; }

        public virtual void Dispose() { }
    }

    [ProtoContract]
    public class NotInvolved : IDisposable
    {
        [ProtoMember(1)]
        public int NotInvolvedProp { get; set; }
        public void Dispose() { }
    }

    [ProtoContract]
    public class AlsoNotInvolved : IDisposable
    {
        [ProtoMember(1)]
        public int AlsoNotInvolvedProp { get; set; }
        public void Dispose() { }
    }
}

Request

This is minor, but it'd be nice if

  (Container<SomeDerived>)model.DeserializeWithLengthPrefix(...) 

could also be implemented like this

  model.DeserializeWithLengthPrefix<Container<SomeDerived>>(...):

btw: I'm starting to dig into the protobuf-net implementation, and I'm starting to notice some interesting methods like this. Something to come back to later I guess:

  public MetaType Add(int fieldNumber, string memberName, Type itemType, Type defaultType);

Discussion:

when I saw the way you could deserialize to an abstract base type in the link above, I thought, yes, that's closer to what was thinking. Could we deserialize to the open generic Container<> first, and then cast more specifically if we need to in different assemblies. Maybe I'm getting mixed up a little here.

You could think of it in terms of Tupple<TBase,TPayload>. Or a variation like Tupple<TBase,Lazy<TPayload>> maybe. It's not that different to List<T>. There are some TreeTypeThings<T> that I have too, but I don't need to serialize/deserialize them (yet).

I had a non-generic sequence working, so it isn't a show stopper. My first implementation could be more efficient. I think I can do better on that with existing protobuf-net features though.

I like the cleaner generic way of working with these ideas. Although I can get to the same destination manually, Generics make other things possible.

re: clarification

everything can be defined ahead of time by the caller. (btw: You've got me thinking about the run-time only scenario now, but no, I don't need that).

like image 324
sgtz Avatar asked Jul 27 '11 17:07

sgtz


1 Answers

So I think the question boils down to "can I use an open generic type as a template for a protobuf model", in which case the answer is "maybe". At the moment, it would see BasicMsg<Foo> and BasicMsg<Bar> as different types, and it would default to using the attribute type model, since it won't recognise them as being defined by [typeof(BasicMsg<>)]. If they have attributes, it'll probably work, but I don't think that was your intention, right?

This is an interesting scenario, and I'm open to discussion on it. However, one particular concern I'd have here is that the nature of generics in .NET means this would require runtime participation, i.e. RuntimeTypeModel. I don't think I could get it working on pre-compiled TypeModel without using MakeGenericMethod which I really want to avoid for both platform and performance reasons. But as a full-.NET runtime-only feature, it looks interesting.

(clarification on the above; if the caller could define all the T for BasicMsg<T> ahead of time, it becomes slightly more doable; then it really comes down to a model template metaphor)

like image 63
Marc Gravell Avatar answered Oct 03 '22 11:10

Marc Gravell