How can I send multiple types of objects across Protobuf?

前端 未结 2 1684
轮回少年
轮回少年 2020-12-30 09:36

I\'m implementing a client-server application, and am looking into various ways to serialize and transmit data. I began working with Xml Serializers, which worked rather wel

相关标签:
2条回答
  • 2020-12-30 10:06

    I've come up with another solution, but I decided to put it as an answer, instead of in the question, because that makes more sense to me. It's pretty ugly, in my opinion, and I've been warned against using reflection, so please comment on it or provide better answers if you have them. Thanks!


    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person
            {
                Id = 12345,
                Name = "Fred",
                Address = new Address
                {
                    Line1 = "Flat 1",
                    Line2 = "The Meadows"
                }
            };
            object value;
            using (Stream stream = new MemoryStream())
            {
                Send<Person>(stream, person);
                stream.Position = 0;
                value = Read(stream);
                person = value as Person;
            }
        }
    
        static void Send<T>(Stream stream, T value)
        {
            Header header = new Header()
            {
                Guid = Guid.NewGuid(),
                Type = typeof(T)
            };
            Serializer.SerializeWithLengthPrefix<Header>(stream, header, PrefixStyle.Base128);
            Serializer.SerializeWithLengthPrefix<T>(stream, value, PrefixStyle.Base128);
        }
    
        static object Read(Stream stream)
        {
    
            Header header;
            header = Serializer.DeserializeWithLengthPrefix<Header>
                (stream, PrefixStyle.Base128);
            MethodInfo m = typeof(Serializer).GetMethod("DeserializeWithLengthPrefix",
                new Type[] {typeof(Stream), typeof(PrefixStyle)}).MakeGenericMethod(header.Type);
            Object value = m.Invoke(null, new object[] {stream, PrefixStyle.Base128} );
            return value;
        }
    }
    
    [ProtoContract]
    class Header
    {
        public Header() { }
    
        [ProtoMember(1, IsRequired = true)]
        public Guid Guid { get; set; }
    
        [ProtoIgnore]
        public Type Type { get; set; }
        [ProtoMember(2, IsRequired = true)]
        public string TypeName
        {
            get { return this.Type.FullName; }
            set { this.Type = Type.GetType(value); }
        }
    }
    
    [ProtoContract]
    class Person {
        [ProtoMember(1)]
        public int Id { get; set; }
        [ProtoMember(2)]
        public string Name { get; set; }
        [ProtoMember(3)]
        public Address Address { get; set; }
    }
    
    [ProtoContract]
    class Address {
        [ProtoMember(1)]
        public string Line1 { get; set; }
        [ProtoMember(2)]
        public string Line2 { get; set; }
    }
    
    0 讨论(0)
  • 2020-12-30 10:28

    This functionality is actually built in, albeit not obviously.

    In this scenario, it is anticipated that you would designate a unique number per message type. The overload you are using passes them all in as "field 1", but there is an overload that lets you include this extra header information (it is still the job of the calling code to decide how to map numbers to types, though). You can then specify different types as different fields is the stream (note: this only works with the base-128 prefix style).

    I'll need to double check, but the intention is that something like the following should work:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using ProtoBuf;
    static class Program
    {
        static void Main()
        {
            using (MemoryStream ms = new MemoryStream())
            {
                WriteNext(ms, 123);
                WriteNext(ms, new Person { Name = "Fred" });
                WriteNext(ms, "abc");
    
                ms.Position = 0;
    
                while (ReadNext(ms)) { }            
            }
        }
        // *** you need some mechanism to map types to fields
        static readonly IDictionary<int, Type> typeLookup = new Dictionary<int, Type>
        {
            {1, typeof(int)}, {2, typeof(Person)}, {3, typeof(string)}
        };
        static void WriteNext(Stream stream, object obj) {
            Type type = obj.GetType();
            int field = typeLookup.Single(pair => pair.Value == type).Key;
            Serializer.NonGeneric.SerializeWithLengthPrefix(stream, obj, PrefixStyle.Base128, field);
        }
        static bool ReadNext(Stream stream)
        {
            object obj;
            if (Serializer.NonGeneric.TryDeserializeWithLengthPrefix(stream, PrefixStyle.Base128, field => typeLookup[field], out obj))
            {
                Console.WriteLine(obj);
                return true;
            }
            return false;
        }
    }
    [ProtoContract] class Person {
        [ProtoMember(1)]public string Name { get; set; }
        public override string ToString() { return "Person: " + Name; }
    }
    

    Note that this doesn't currently work in the v2 build (since the "WithLengthPrefix" code is incomplete), but I'll go and test it on v1. If it works, I'll all the above scenario to the test suite to ensure it does work in v2.

    Edit:

    yes, it does work fine on "v1", with output:

    123
    Person: Fred
    abc
    
    0 讨论(0)
提交回复
热议问题