Protobuf-net serialization without annotation

后端 未结 3 1916
一个人的身影
一个人的身影 2020-12-08 01:19

I looked at this answer and I am in a situation where I don\'t need to maintain backward compatibility and I have to have a solution that works without having to decorate do

相关标签:
3条回答
  • 2020-12-08 01:34

    It's quite experimental at this phase but I've made a small library that takes most Types and generates the Protobuf-net serializers at run-time: https://github.com/fnicollier/AutoProtobuf

    0 讨论(0)
  • 2020-12-08 01:41

    The question is old, but maybe someone will need this. I implemented ProtobufSerializer class which will construct your Type graph upon use. You just need to annotate your DTO with [KnownTypeAttribute] and [DataMember]/[IgnoreDataMember] attributes. Mostly this is refactored version of another nuget project from some guy. This way you don't need to include protobuf in your contract dependencies:

        internal sealed class ProtobufSerializer
        {
            private readonly RuntimeTypeModel _model;
            private const BindingFlags Flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
            private readonly Dictionary<Type, HashSet<Type>> _subTypes = new Dictionary<Type, HashSet<Type>>();
            private readonly ConcurrentDictionary<Type, bool> _builtTypes = new ConcurrentDictionary<Type, bool>();
            private static readonly Type[] ComplexPrimitives = new [] { typeof(object), typeof(ValueType), typeof(Enum), typeof(Array)};
            private readonly object _sync = new object();
    
            public ProtobufSerializer()
            {
                _model = TypeModel.Create();
            }
    
            public void Serialize(Stream s, object input)
            {
                EnsureType(input.GetType());
                _model.Serialize(s, input);
            }
    
            public T Deserialize<T>(Stream s)
            {
                EnsureType(typeof(T));
                return (T)_model.Deserialize(s, null, typeof(T));
            }
    
            public void EnsureType(Type type)
            {
                if (_builtTypes.ContainsKey(type))
                {
                    return;
                }
                lock (_sync)
                {
                    if (_builtTypes.ContainsKey(type))
                    {
                        return;
                    }
                    var all = GetGraph(type).ToArray();
                    foreach (var t in all)
                    {
                        InternalBuild(t);
                    }
                }
            }
    
            private void InternalBuild(Type type)
            {
                if (IsPrimitive(type))
                {
                    return;
                }
    
                FlatBuild(type);
                EnsureBaseClasses(type);
                EnsureGenerics(type);
    
                _builtTypes.TryAdd(type, false);
            }
    
            private bool IsPrimitive(Type type)
            {
                return type == null || type.IsPrimitive || _model.CanSerializeBasicType(type) || _builtTypes.ContainsKey(type) || ComplexPrimitives.Contains(type);
            }
    
            private static IEnumerable<Type> GetGraph(Type type)
            {
                return type.TraverseDistinct(GetConnections).Distinct().OrderBy(x=> x.FullName);
            }
    
            private static Type GetParent(Type type)
            {
                return type.BaseType;
            }
    
            private static IEnumerable<Type> GetChildren(Type type)
            {
                var knownTypes = type.GetCustomAttributes(typeof(KnownTypeAttribute)).Cast<KnownTypeAttribute>().Select(x => x.Type).ToArray();
                foreach (var t in knownTypes)
                {
                    yield return t;
                }
    
                var fields = GetFields(type);
                var props = GetProperties(type);
                foreach (var memberType in fields.Select(f => f.FieldType))
                {
                    yield return memberType;
                }
                foreach (var memberType in props.Select(f => f.PropertyType))
                {
                    yield return memberType;
                }
            }
    
            private static IEnumerable<Type> GetConnections(Type type)
            {
                var parent = GetParent(type);
                if (parent != null)
                {
                    yield return parent;
                }
                var children = GetChildren(type);
                if (children != null)
                {
                    foreach (var c in children)
                    {
                        yield return c;
                    }
                }
            }
    
            private void FlatBuild(Type type)
            {
                if(type.IsAbstract)
                    return;
    
                var meta = _model.Add(type, false);
                var fields = GetFields(type);
                var props = GetProperties(type);
                meta.Add(fields.Select(m => m.Name).ToArray());
                meta.Add(props.Select(m => m.Name).ToArray());
                meta.UseConstructor = false;
                foreach (var memberType in fields.Select(f => f.FieldType).Where(t => !t.IsPrimitive))
                {
                    InternalBuild(memberType);
                }
                foreach (var memberType in props.Select(f => f.PropertyType).Where(t => !t.IsPrimitive))
                {
                    InternalBuild(memberType);
                }
            }
    
            private static FieldInfo[] GetFields(Type type)
            {
                return type.GetFields(Flags).Where(x => x.IsDefined(typeof(DataMemberAttribute))).Where(x => !x.IsDefined(typeof(IgnoreDataMemberAttribute))).ToArray();
            }
    
            private static PropertyInfo[] GetProperties(Type type)
            {
                return type.GetProperties(Flags).Where(x => x.IsDefined(typeof(DataMemberAttribute))).Where(x=> !x.IsDefined(typeof(IgnoreDataMemberAttribute))).ToArray();
            }
    
            private void EnsureBaseClasses(Type type)
            {
                var baseType = type.BaseType;
                var inheritingType = type;
    
    
                while (!IsPrimitive(baseType))
                {
                    HashSet<Type> baseTypeEntry;
    
                    if (!_subTypes.TryGetValue(baseType, out baseTypeEntry))
                    {
                        baseTypeEntry = new HashSet<Type>();
                        _subTypes.Add(baseType, baseTypeEntry);
                    }
    
                    if (!baseTypeEntry.Contains(inheritingType))
                    {
                        InternalBuild(baseType);
                        _model[baseType].AddSubType(baseTypeEntry.Count + 500, inheritingType);
                        baseTypeEntry.Add(inheritingType);
                    }
    
                    inheritingType = baseType;
                    baseType = baseType.BaseType;
                }
            }
    
            private void EnsureGenerics(Type type)
            {
                if (type.IsGenericType || (type.BaseType != null && type.BaseType.IsGenericType))
                {
                    var generics = type.IsGenericType ? type.GetGenericArguments() : type.BaseType.GetGenericArguments();
    
                    foreach (var generic in generics)
                    {
                        InternalBuild(generic);
                    }
                }
            }
        }
    

    And also some simple extensions:

        public static IEnumerable<T> TraverseDistinct<T>(this T enumer, Func<T, IEnumerable<T>> getChildren)
        {
            return new[] { enumer }.TraverseDistinct(getChildren);
        }
        public static IEnumerable<T> TraverseDistinct<T>(this IEnumerable<T> enumer, Func<T, IEnumerable<T>> getChildren)
        {
            HashSet<T> visited = new HashSet<T>();
            Stack<T> stack = new Stack<T>();
            foreach (var e in enumer)
            {
                stack.Push(e);
            }
            while (stack.Count > 0)
            {
                var i = stack.Pop();
                yield return i;
                visited.Add(i);
    
                var children = getChildren(i);
                if (children != null)
                {
                    foreach (var child in children)
                    {
                        if (!visited.Contains(child))
                        {
                            stack.Push(child);
                        }
                    }
                }
            }
        }
    
    0 讨论(0)
  • 2020-12-08 01:53

    InferTagFromName (and it's twin, InferTagFromNameDefault) only take a hand when it is necessary to resolve a tag number for a member; they don't influence which members need to be serialized (so currently the answer to that would be: none, even if the system knew about them). The option you might have chosen would be ImplicitFields, but that is currently only available as a [ProtoContract(...)] marker. If you don't mind a little annotation, a pragmatic fix may be:

    [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
    

    on User, Company and Product, and something a bit more complex for BaseUser (because of the inheritance):

    [ProtoContract(ImplicitFields = ImplicitFields.AllPublic, ImplicitFirstTag = 10)]
    [ProtoInclude(1, typeof(User))]
    

    Note we haven't had to add lots of per-member annotation. If you are really really anti-attributes, then it is also possible to configure the entire model through code, via:

    RuntimeTypeModel.Default.Add(typeof(Product), false).Add("Name", "Sku");
    RuntimeTypeModel.Default.Add(typeof(Company), false).Add("Name", "Address",
             "Type", "Products");
    RuntimeTypeModel.Default.Add(typeof(User), false).Add("FirstName", "LastName",
             "Age", "BirthDate", "Friends", "Company");
    RuntimeTypeModel.Default.Add(typeof(BaseUser), false).Add(10, "SSN")
             .AddSubType(1, typeof(User));
    
    0 讨论(0)
提交回复
热议问题