【问题标题】:Protobuf-net serialization without annotation没有注释的 Protobuf-net 序列化
【发布时间】:2012-08-31 18:03:27
【问题描述】:

我查看了this 的答案,我处于不需要保持向后兼容性的情况,我必须有一个可以工作的解决方案,而无需使用 protobuf-net 所需的属性来装饰数十个类.所以我尝试使用RuntimeTypeModel.Default.InferTagFromNameDefault = true;,但我可能没有正确使用它,因为 Serializer.Serialize 调用仍然会引发异常,要求签订合同。这是我的快速测试,我做错了什么?

public enum CompanyTypes
{
    None, Small, Big, Enterprise, Startup
}

public class BaseUser
{
    public string SSN { get; set; }    
}

public class User : BaseUser
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
    public DateTime BirthDate { get; set; }
    public List<string> Friends { get; set; }
    public Company Company { get; set; }
}

public class Company
{
    public string Name { get; set; }
    public string Address { get; set; }
    public CompanyTypes Type { get; set; }
    public List<Product> Products { get; set; }
}

public class Product
{
    public string Name { get; set; }
    public string Sku { get; set; }
}

[TestClass]
public class SerializationTest
{
    [TestMethod]
    public void SerializeDeserializeTest()
    {
        var user = new User
                       {
                           Age = 10,
                           BirthDate = DateTime.Now.AddYears(-10),
                           FirstName = "Test First",
                           LastName = "Test Last",
                           Friends = new List<string> { "Bob", "John" },
                           Company = new Company
                                         {
                                             Name = "Test Company",
                                             Address = "Timbuktu",
                                             Type = CompanyTypes.Startup,
                                             Products = new List<Product>
                                             {
                                                new Product{Name="Nerf Rocket", Sku="12324AC"},
                                                new Product{Name="Nerf Dart", Sku="DHSN123"}
                                             }
                                         }
                       };

        RuntimeTypeModel.Default.InferTagFromNameDefault = true;
        using (var memoryStream = new MemoryStream())
        {
            Serializer.Serialize(memoryStream, user);
            var serialized = Convert.ToBase64String(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
        }
    }
}

【问题讨论】:

    标签: c# serialization annotations protobuf-net


    【解决方案1】:

    InferTagFromName(它是双胞胎,InferTagFromNameDefault)仅在需要为成员解析标签号时才出手;它们不会影响需要序列化的成员(因此目前的答案是:没有,即使系统知道它们)。您可能选择的选项是ImplicitFields,但目前只能用作[ProtoContract(...)] 标记。如果您不介意 little 注释,实用的解决方法可能是:

    [ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
    

    UserCompanyProduct 上,对于BaseUser 来说更复杂一些(因为继承):

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

    请注意,我们不必为每个成员添加大量注释。如果你真的是真的反属性,那么也可以通过代码来配置整个模型,通过:

    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));
    

    【讨论】:

    • 感谢马克的详细回复。不幸的是,最小注解或 RuntimeTypeModel 路由对我们不起作用,因为我们的框架被公司内外的开发人员使用,我们不希望他们必须引用 Protobuf 程序集并且必须装饰他们继承的类。一种选择是使用反射来处理 RuntimeTypeModel,这不一定是微不足道的,所以现在我要回到旧的(和慢的)BinaryFormatter。你有没有计划让 Protobuf 在没有任何注释的情况下工作?再次感谢您在 Protobuf 上所做的工作,太棒了!
    • @codelove 我想做的是向模型添加一个事件,该事件在找到新类型时触发,允许您的代码做出反应并配置它发现时 - 会有帮助吗?我还需要做的是使“隐式字段”行为可以通过模型访问,而不仅仅是通过属性。
    • Marc:是的,每个发现的新类型的事件都会让它更容易一些,但我仍然需要一些管道来预加载或延迟加载所有类型及其属性和字段反射并将它们推入模型中。所以这个事件本身不会有太大帮助。但是,结合您通过模型允许“隐式字段”的其他建议。因为那时我可以订阅该事件,并且在发现每种类型时,我会告诉它隐式地拾取字段。这种方法让我不必考虑每种类型。
    • Marc:我不确定你是不是唯一一个参与这个项目的人,但如果你需要帮助,我很乐意添加这篇文章。
    • @MarcGravell 在这种情况下我遇到了类似的问题:stackoverflow.com/questions/57116127/…
    【解决方案2】:

    在这个阶段它是相当实验性的,但我制作了一个小型库,它采用大多数类型并在运行时生成 Protobuf-net 序列化程序:https://github.com/fnicollier/AutoProtobuf

    【讨论】:

      【解决方案3】:

      这个问题很老了,但也许有人会需要这个。我实现了 ProtobufSerializer 类,它将在使用时构造您的类型图。您只需要使用 [KnownTypeAttribute][DataMember]/[IgnoreDataMember] 属性来注释您的 DTO。大多数情况下,这是某个人对另一个 nuget 项目的重构版本。这样你就不需要在你的合约依赖中包含 protobuf:

          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);
                      }
                  }
              }
          }
      

      还有一些简单的扩展:

          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);
                          }
                      }
                  }
              }
          }
      

      【讨论】:

        猜你喜欢
        • 2021-01-14
        • 2013-06-16
        • 2023-04-02
        • 2011-10-03
        • 1970-01-01
        • 2015-09-10
        • 2012-04-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多