Protobuf attributes with a hierarchy of generic classes

后端 未结 2 361
臣服心动
臣服心动 2020-12-21 15:40

I have a class hierarchy that looks like this. These classes contain a lot of other details which I have excluded. This is a simplification to focus on the serialization a

相关标签:
2条回答
  • 2020-12-21 16:10

    OK; with the updated question I understand a bit more. I expect the generics in the middle of the object-model are indeed going to make life... "fun". It doesn't work "out of the box"; I looked to see if there were some simple tweaks I could make to support it, but it started getting ugly pretty quickly. I expect it would be better to simply remove the need for the generic in the middle, if possible - perhaps retaining a generic interface (rather than the generic class). Here's some code that does work; how this maps to your code... I can't tell 100%. Note you don't have to use the TypeDescriptor stuff (etc) - it just seemed that since you are using code-gen this might make some things easier...

    (I didn't check the DataSet stuff - just the class stuff)

    using System;
    using System.ComponentModel;
    using System.Data;
    using System.IO;
    using NUnit.Framework;
    using ProtoBuf;
    
    [TestFixture]
    public class ComplexGenericTest
    {
        [Test]
        public void TestX()
        {
            Query query = new X { Result = "abc" };
            Assert.AreEqual(typeof(string), query.GetQueryType());
            Query clone = Serializer.DeepClone<Query>(query);
            Assert.IsNotNull(clone);
            Assert.AreNotSame(clone, query);
            Assert.IsInstanceOfType(query.GetType(), clone);
            Assert.AreEqual(((X)query).Result, ((X)clone).Result);
        }
        [Test]
        public void TestY()
        {
            Query query = new Y { Result = 1234};
            Assert.AreEqual(typeof(int), query.GetQueryType());
            Query clone = Serializer.DeepClone<Query>(query);
            Assert.IsNotNull(clone);
            Assert.AreNotSame(clone, query);
            Assert.IsInstanceOfType(query.GetType(), clone);
            Assert.AreEqual(((Y)query).Result, ((Y)clone).Result);
        }
    
    }
    public static class QueryExt {
        public static Type GetQueryType(this IQuery query)
        {
            if (query == null) throw new ArgumentNullException("query");
            foreach (Type type in query.GetType().GetInterfaces())
            {
                if (type.IsGenericType
                    && type.GetGenericTypeDefinition() == typeof(IQuery<>))
                {
                    return type.GetGenericArguments()[0];
                }
            }
            throw new ArgumentException("No typed query implemented", "query");
        }
    }
    public interface IQuery
    {
        string Result { get; set; }
    }
    public interface IQuery<T> : IQuery
    {
        new T Result { get; set; }
    }
    
    [ProtoInclude(21, typeof(W))]
    [ProtoInclude(22, typeof(X))]
    [ProtoInclude(23, typeof(Y))]
    [ProtoInclude(25, typeof(SpecialQuery))]
    [ProtoContract]
    abstract class Query : IQuery
    {
        public string Result
        {
            get { return ResultString; }
            set { ResultString = value; }
        }
        protected abstract string ResultString { get; set; }
    
        // these are to allow simple ResultString implementations
        // without the codegen having to worry about int.Parse etc
        protected static string FormatQueryString<T>(T value)
        {
            return TypeDescriptor.GetConverter(typeof(T))
                .ConvertToInvariantString(value);
        }
        protected static T ParseQueryString<T>(string value)
        {
            return (T) TypeDescriptor.GetConverter(typeof(T))
                .ConvertFromInvariantString(value);
        }
    }
    [ProtoContract]
    [ProtoInclude(21, typeof(Z))]
    abstract class SpecialQuery : Query, IQuery<DataSet>
    {
    
        public new DataSet Result { get; set; }
    
        [ProtoMember(1)]
        protected override string ResultString
        {
            get {
                if (Result == null) return null;
                using (StringWriter sw = new StringWriter())
                {
                    Result.WriteXml(sw, XmlWriteMode.WriteSchema);
                    return sw.ToString();
                }
            }
            set {
                if (value == null) { Result = null; return; }
                using (StringReader sr = new StringReader(value))
                {
                    DataSet ds = new DataSet();
                    ds.ReadXml(sr, XmlReadMode.ReadSchema);
                }
            }
        }
    }
    
    [ProtoContract]
    class W : Query, IQuery<bool>
    {
        [ProtoMember(1)]
        public new bool Result { get; set; }
    
        protected override string ResultString
        {
            get {return FormatQueryString(Result); }
            set { Result = ParseQueryString<bool>(value); }
        }
    }
    [ProtoContract]
    class X : Query, IQuery<string>
    {
        [ProtoMember(1)]
        public new string Result { get; set; }
    
        protected override string ResultString
        {
            get { return Result ; }
            set { Result = value; }
        }
    }
    [ProtoContract]
    class Y : Query, IQuery<int>
    {
        [ProtoMember(1)]
        public new int Result { get; set; }
    
        protected override string ResultString
        {
            get { return FormatQueryString(Result); }
            set { Result = ParseQueryString<int>(value); }
        }
    }
    [ProtoContract]
    class Z : SpecialQuery
    {
    }
    
    0 讨论(0)
  • 2020-12-21 16:22

    I'm not 100% sure I understand the scenario you want to model; however, [ProtoInclude] only looks one level of inheritance away.

    If I understand correctly, try the following; note that you would need to know the potential generic types at compile time at the moment:

    using System;
    using ProtoBuf;
    [ProtoContract]
    [ProtoInclude(2, typeof(Response))]
    [ProtoInclude(3, typeof(Query))]
    class Packet
    {
        [ProtoMember(1)]
        int ID;
    }
    [ProtoContract]
    [ProtoInclude(1, typeof(Response<int>))]
    [ProtoInclude(2, typeof(Response<decimal>))]
    [ProtoInclude(3, typeof(Response<string>))]
    class Response : Packet
    {
    }
    [ProtoContract]
    class Response<T> : Response
    {
        [ProtoMember(2)]
        public T Value;
    
        public override string ToString()
        {
            return typeof(T).Name + ": " + Value;
        }
    }
    static class Program
    {
        static void Main()
        {
            Packet packet = new Response<int> { Value = 123 };
            Packet clone = Serializer.DeepClone<Packet>(packet);
            Console.WriteLine(clone.ToString()); // should be int/123
        }
    }
    
    0 讨论(0)
提交回复
热议问题