Protobuf-net:如何序列化复杂的集合?

Protobuf-net: How to serialize complex collection?

我正在尝试使用 protobuf-net 序列化此类对象:

[ProtoContract]
public class RedisDataObject
{
    [ProtoMember(1)]
    public string DataHash;
    [ProtoMember(2, DynamicType = true)]
    public Dictionary<ContextActions, List<Tuple<string, List<object>>>> Value;
}

[Serializable]
public enum ContextActions
{
    Insert,
    Update,
    Delete
}

我正在使用 List<object>,因为我在代码中存储了其他 class 的不同 class 实例。

但我收到此错误消息:

Unable to resolve a suitable Add method for System.Collections.Generic.Dictionary...

这显然是字典的问题,但我找不到如何解决这个问题的解决方案。

您的基本问题是 DynamicType = true 仅适用于特定的 属性,并且仅针对特定的 属性 的值序列化类型信息。它不会递归地应用于该对象包含的任何属性。但是,您的 object 值嵌套在容器的多个级别中:

public Dictionary<ContextActions, List<Tuple<string, List<object>>>> Value;

您需要做的是在这个列表元组字典中为每个 object 序列化类型信息。您可以通过引入代理值类型来做到这一点:

[ProtoContract]
public struct DynamicTypeSurrogate<T>
{
    [ProtoMember(1, DynamicType = true)]
    public T Value { get; set; }
}

public static class DynamicTypeSurrogateExtensions
{
    public static List<DynamicTypeSurrogate<T>> ToSurrogateList<T>(this IList<T> list)
    {
        if (list == null)
            return null;
        return list.Select(i => new DynamicTypeSurrogate<T> { Value = i }).ToList();
    }

    public static List<T> FromSurrogateList<T>(this IList<DynamicTypeSurrogate<T>> list)
    {
        if (list == null)
            return null;
        return list.Select(i => i.Value).ToList();
    }
}

然后修改您的 RedisDataObject 以序列化代理字典,如下所示:

[ProtoContract]
public class RedisDataObject
{
    [ProtoMember(1)]
    public string DataHash;

    [ProtoIgnore]
    public Dictionary<ContextActions, List<Tuple<string, List<object>>>> Value;

    [ProtoMember(2)]
    private Dictionary<ContextActions, List<Tuple<string, List<DynamicTypeSurrogate<object>>>>> SurrogateValue
    {
        get
        {
            if (Value == null)
                return null;
            var dictionary = Value.ToDictionary(
                p => p.Key,
                p => (p.Value == null ? null : p.Value.Select(i => Tuple.Create(i.Item1, i.Item2.ToSurrogateList())).ToList()));
            return dictionary;
        }
        set
        {
            if (value == null)
                Value = null;
            else
            {
                Value = value.ToDictionary(
                    p => p.Key,
                    p => (p.Value == null ? null : p.Value.Select(i => Tuple.Create(i.Item1, i.Item2.FromSurrogateList())).ToList()));
            }
        }
    }
}

另请注意 here 中提到的 DynamicType 的限制:

DynamicType - stores additional Type information with the type (by default it includes the AssemblyQualifiedName, although this can be controlled by the user). This makes it possible to serialize weak models, i.e. where object is used for property members, however currently this is limited to contract types (not primitives), and does not work for types with inheritance (these limitations may be removed at a later time). Like with AsReference, this uses a very different layout format

虽然上述文档存在于 former project site and has not been moved to the current site,但从版本 2.0.0.668 开始,对非合约类型的限制肯定仍然存在。 (我测试过给List<object>加一个int值失败;我没有检查是否还有继承限制。)

dbc 的帮助下,以及他的回答和评论中提到的所有链接

[ProtoContract]
[ProtoInclude(1, typeof(ObjectWrapper<int>))]
[ProtoInclude(2, typeof(ObjectWrapper<decimal>))]
[ProtoInclude(3, typeof(ObjectWrapper<DateTime>))]
[ProtoInclude(4, typeof(ObjectWrapper<string>))]
[ProtoInclude(5, typeof(ObjectWrapper<double>))]
[ProtoInclude(6, typeof(ObjectWrapper<long>))] 
[ProtoInclude(8, typeof(ObjectWrapper<Custom>))]
[ProtoInclude(9, typeof(ObjectWrapper<CustomType[]>))]
public abstract class ObjectWrapper
{
  protected ObjectWrapper() { }
  abstract public object ObjectValue { get; set; }

  public static ObjectWrapper Create(object o) {
    Type objectType = o.GetType();
    Type genericType = typeof(ObjectWrapper<>);
    Type specializedType = genericType.MakeGenericType(objectType);
    return (ObjectWrapper)Activator.CreateInstance(specializedType, new object[] { o });
  }
}

缺点是您必须在对象列表中注册您正在使用的所有类型。每次 ProtoInclude 系列中未包含的新类型出现时,您都会收到 InvalidOperationException 消息 Unexpected sub-type: ObjectWrapper`1[[NewType]].

[ProtoContract]
public class RedisDataObjectWrapper {
  [ProtoMember(1)] public string DataHash;
  [ProtoIgnore] public Dictionary<ContextActions, List<Tuple<string, List<object>>>> Value;

[ProtoMember(2)] 
private Dictionary<ContextActions, List<Tuple<string, List<ObjectWrapper>>>> AdaptedValue {
  get {
    if (Value == null) return null;
    var dictionary = Value.ToDictionary(
        p => p.Key,
        p => (p.Value == null ? null : p.Value.Select(i => Tuple.Create(i.Item1, i.Item2.Select(x=>ObjectWrapper.Create(x)).ToList() )).ToList()));
    return dictionary;
  }
  set {
    if (value == null) Value = null;
    else {
      Value = value.ToDictionary(
          p => p.Key,
          p => (p.Value == null ? null : p.Value.Select(i => Tuple.Create(i.Item1, i.Item2.Select(x=>x.ObjectValue).ToList() )).ToList()));
    } } } }