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

时间:2016-08-29 14:14:43

标签: c# serialization protobuf-net

我尝试使用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>,因为我在我的代码中存储了其他类的不同类实例。

但我收到此错误消息:

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

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

2 个答案:

答案 0 :(得分:5)

您的基本问题是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()));
            }
        }
    }
}

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

  

DynamicType - 存储类型的其他Type信息(默认情况下,它包含AssemblyQualifiedName,尽管这可以由用户控制)。这使得序列化弱模型成为可能,即object用于属性成员,但是目前这仅限于契约类型(不是原语),并且不适用于具有继承的类型(这些限制可能会在以后删除)。与AsReference一样,它使用非常不同的布局格式

虽然上面的文档存在于former project site但尚未移至current site,但自版本2.0.0.668起,对非合同类型的限制仍然存在。 (我测试过向int添加List<object>值失败了;我还没有检查对继承的限制是否仍然存在。)

答案 1 :(得分:0)

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