我查看this回答,我处于不需要保持向后兼容性的情况下,我必须有一个解决方案,无需用protobuf所需的属性装饰数十个类-净。所以我尝试使用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);
}
}
}
答案 0 :(得分:26)
InferTagFromName
(并且它是双胞胎,InferTagFromNameDefault
)只有在需要解析会员的标签号时才会牵手;它们不会影响哪些成员需要序列化(所以目前答案就是:没有,即使系统知道它们)。您可能选择的选项为ImplicitFields
,但目前仅作为[ProtoContract(...)]
标记提供。如果您不介意 little 注释,那么实用的修复可能是:
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic)]
<{1>},User
和Company
上的以及Product
更复杂的事情(因为继承):
BaseUser
请注意,我们不必添加大量的每个成员注释。如果你真的是反属性,那么也可以通过代码配置整个模型,通过:
[ProtoContract(ImplicitFields = ImplicitFields.AllPublic, ImplicitFirstTag = 10)]
[ProtoInclude(1, typeof(User))]
答案 1 :(得分:3)
问题是陈旧的,但也许有人需要这个。我实现了 ProtobufSerializer 类,它将在使用时构建您的Type图。您只需使用 [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);
}
}
}
}
}
答案 2 :(得分:2)
这个阶段非常具有实验性,但是我创建了一个小型库,它可以获取大多数类型并在运行时生成Protobuf-net序列化器:https://github.com/fnicollier/AutoProtobuf