使用Dictionary的Protobuf-net对象引用反序列化:引用跟踪对象在反序列化期间更改了引用

Lee*_*e F 9 c# serialization protobuf-net

我在尝试使用protobuf-net序列化/反序列化复杂对象图时遇到了一些问题.

我正在研究遗留应用程序,我们正在使用.Net Remoting将GUI客户端连接到C#服务.由于使用默认值的对象图的序列化大小,我们看到海外用户的性能不佳BinaryFormatter,客户端和服务器之间的有限带宽(1Mbit/s)加剧了这种情况.

作为一个快速的胜利,我想我已经汇总了一个概念证明,通过实施,使用protobuf-net代替是否有任何性能提升ISerializable.在测试时,我遇到了一个问题,即没有维护对象引用.

我汇总了一个重新解决问题的例子.我期待Dictionary(Items [1])和对象BA中的对象与我AsReference=trueProtoMember属性中指定的对象相同 .

使用protobuf-net 2.0.0.619,我看到反序列化时引发的异常(反序列化过程中引用跟踪对象更改了引用).

如果这不是支持的方案,请告诉我.

测试

[Test]
public void AreObjectReferencesSameAfterDeserialization()
{
    A a = new A();
    B b = new B();

    b.A = a;

    b.Items.Add(1, a);

    Assert.AreSame(a, b.A);
    Assert.AreSame(b.A, b.Items[1]);

    B deserializedB;

    using (var stream = new MemoryStream())
    {
        Serializer.Serialize(stream, b);
        stream.Seek(0, SeekOrigin.Begin);
        deserializedB = Serializer.Deserialize<B>(stream);
    }

    Assert.AreSame(deserializedB.A, deserializedB.Items[1]);
}
Run Code Online (Sandbox Code Playgroud)

类定义

[Serializable]
[ProtoContract]
public class A
{
}

[Serializable]
[ProtoContract]
public class B
{
    [ProtoMember(1, AsReference = true)]
    public A A { get; set; }

    [ProtoMember(2, AsReference = true)]
    public Dictionary<int, A> Items { get; set; }

    public B()
    {
        Items = new Dictionary<int, A>();
    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ell 4

编辑:这应该从下一个版本开始工作,只需标记类型AsReferenceDefault

[ProtoContract(AsReferenceDefault=true)]
public class A
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)

目前,这是一种不受支持的场景 - 至少在属性上是不受支持的;基本上,AsReference=true current指的是KeyValuePair<int,A>,这实际上没有意义,因为KeyValuePair<int,A>它是一个值类型(所以这永远不能被视为参考;我在我的本地副本中为此添加了更好的消息)。

因为KeyValuePair<int,A>(默认情况下)充当tuple,所以目前没有地方可以支持该AsReference信息,但这是我想更好支持的场景,我将对此进行调查。

还有一个错误意味着AsReference元组(甚至引用类型元组)变得无序,但我已经在本地修复了这个问题;这就是“已更改”消息的来源。

理论上,我做这件事的工作量并不大;基本原理已经起作用了,奇怪的是,昨晚它也在推特上单独出现——我想“字典指向一个对象”是一个非常常见的场景。据猜测,我想我会添加一些属性来帮助描述这种情况,但实际上您现在可以使用几种不同的路线来解决它:

1:KeyValuePair<int,A>手动配置:

[Test]
public void ExecuteHackedViaFields()
{
    // I'm using separate models **only** to keep them clean between tests;
    // normally you would use RuntimeTypeModel.Default
    var model = TypeModel.Create();

    // configure using the fields of KeyValuePair<int,A>
    var type = model.Add(typeof(KeyValuePair<int, A>), false);
    type.Add(1, "key");
    type.AddField(2, "value").AsReference = true;

     // or just remove AsReference on Items
    model[typeof(B)][2].AsReference = false;

    Execute(model);
}
Run Code Online (Sandbox Code Playgroud)

我不太喜欢这个,因为它利用了KeyValuePair<,>(私有字段)的实现细节,并且可能无法在 .NET 版本之间工作。我更愿意通过代理即时替换KeyValuePair<,>

[Test]
public void ExecuteHackedViaSurrogate()
{
    // I'm using separate models **only** to keep them clean between tests;
    // normally you would use RuntimeTypeModel.Default
    var model = TypeModel.Create();

    // or just remove AsReference on Items
    model[typeof(B)][2].AsReference = false;

    // this is the evil bit: configure a surrogate for KeyValuePair<int,A>
    model[typeof(KeyValuePair<int, A>)].SetSurrogate(typeof(RefPair<int, A>));
    Execute(model);
}

[ProtoContract]
public struct RefPair<TKey,TValue> {
    [ProtoMember(1)]
    public TKey Key {get; private set;}
    [ProtoMember(2, AsReference = true)]
    public TValue Value {get; private set;}
    public RefPair(TKey key, TValue value) : this() {
        Key = key;
        Value = value;
    }
    public static implicit operator KeyValuePair<TKey,TValue>
        (RefPair<TKey,TValue> val)
    {
        return new KeyValuePair<TKey,TValue>(val.Key, val.Value);
    }
    public static implicit operator RefPair<TKey,TValue>
        (KeyValuePair<TKey,TValue> val)
    {
        return new RefPair<TKey,TValue>(val.Key, val.Value);
    }
}
Run Code Online (Sandbox Code Playgroud)

这配置了要使用的东西 KeyValuePair<int,A>通过运算符转换)。

在这两者中,Execute只是:

private void Execute(TypeModel model)
{
    A a = new A();
    B b = new B();

    b.A = a;

    b.Items.Add(1, a);

    Assert.AreSame(a, b.A);
    Assert.AreSame(b.A, b.Items[1]);

    B deserializedB = (B)model.DeepClone(b);

    Assert.AreSame(deserializedB.A, deserializedB.Items[1]);
}
Run Code Online (Sandbox Code Playgroud)

不过,我确实想添加直接支持。上述两者的好处是,当我有时间这样做时,您只需删除自定义配置代码即可。

为了完整起见,如果您的代码使用方法,那么您应该配置默认Serializer.*模型,而不是创建/配置模型:

RuntimeTypeModel.Default.Add(...); // etc
Run Code Online (Sandbox Code Playgroud)

Serializer.*基本上是一个捷径RuntimeTypeModel.Default.*

最后:你不应该TypeModel为每次调用创建一个新的;这会损害性能。您应该创建并配置一个模型实例,并多次重复使用它。或者只使用默认模型。