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

3
我正在尝试使用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个回答

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的限制:

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

尽管上述文档存在于旧项目网站,并且尚未迁移到当前网站,但是非合同类型的限制在版本2.0.0.668中仍然存在。(我测试了将int值添加到List<object>会失败;我没有检查继承限制是否仍然存在。)


我获得了一个“InvalidOperationException”异常,上面写着“动态类型不是合同类型:<type name>”,这是由于包含双精度、小数、日期和字符串的对象列表。 - andrei.ciprian
@andrei.ciprian - 这听起来像是这个问题中描述的问题。 - dbc
我喜欢你的方法,但它不适用于任何类型。你建议的问题可能会像这个一样有效:[https://dev59.com/YZnga4cB1Zd3GeqPdcZ_#38914841]。 - andrei.ciprian
@andrei.ciprian - 当每个对象都是其他类的类实例时,它可以像原始问题中提到的那样工作--即映射到数据合同类型的一些POCO。我使用了一些示例[ProtoContract]类进行测试。但也许我可以将其与这里的答案结合起来,消除对非合同类型的限制。 - dbc
@andrei.ciprian,您需要在类上添加属性[ProtoContract]。请查看这里以了解如何设置它。基本上,您需要将属性[ProtoMember(N)]添加到要使用的字段中,或者只需像这样使用ImplicitFields注释:[ProtoContract(ImplicitFields = ImplicitFields.AllFields)]来包括类中的所有字段。此注释ImplicitFields具有更多选项,因此您可以根据需要探索和使用。 - Liran Friedman
@dbc - 非常好用,非常感谢您提供的这个出色的解决方案和说明。 - Liran Friedman

1

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系列中的新类型时,您将收到一个带有消息Unexpected sub-type: ObjectWrapper`1[[NewType]]InvalidOperationException异常。

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

这个解决方案对我非常有效!对于所有使用此解决方案的人,您需要创建一个ObjectWrapper<T>类。以下是我使用的实现。[ProtoContract] public class ObjectWrapper<T> : ObjectWrapper { public ObjectWrapper() :base(){ } public ObjectWrapper(object val) :this() { ObjectValue = val; } public override object ObjectValue { get => Value; set => Value = (T)value; }[ProtoMember(1)] public T Value { get; set; }} - John C

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接