使用字典的 Protobuf-net 对象引用反序列化:一个引用跟踪对象在反序列化期间更改了引用 [英] Protobuf-net object reference deserialization using Dictionary: A reference-tracked object changed reference during deserialization

查看:45
本文介绍了使用字典的 Protobuf-net 对象引用反序列化:一个引用跟踪对象在反序列化期间更改了引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm having some issues trying to serialize/deserialize a complex object graph using protobuf-net.

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

I'm working on a legacy application and we're using .Net Remoting to connect a GUI client to a C# service. We are seeing poor performance with overseas users due to the serialized size of our object graphs using the default BinaryFormatter, which is exacerbated by the limited bandwidth in-between the client and server (1Mbit/s).

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

As a quick win, I thought I'd put together a proof of concept to see if there were any performance gains to be had by using protobuf-net instead, by implementing ISerializable. As I was testing I ran into an issue whereby object references weren't being maintained.

我整理了一个例子来重现这个问题.我期望 Dictionary (Items[1]) 中的对象和对象 BA 将与我在 AsReference=true 中指定的相同>ProtoMember 属性.

I've put together an example which repros the issue. I'm expecting that the object in the Dictionary (Items[1]) and the object B.A will be the same as I've specified AsReference=true in the ProtoMember attribute.

使用 protobuf-net 2.0.0.619,我看到反序列化时抛出异常(反序列化期间引用跟踪对象更改了引用).

Using protobuf-net 2.0.0.619, I'm seeing an exception thrown when deserializing (A reference-tracked object changed reference during deserialization).

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

If this isn't a supported scenario the please let me know.

测试

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

类定义

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

推荐答案

只需标记类型的 AsReferenceDefault:

[ProtoContract(AsReferenceDefault=true)]
public class A
{
    // ...
}

<小时>

目前,这是某种不受支持的场景 - 至少,通过属性,它不受支持;基本上,AsReference=true currently 指的是 KeyValuePair,因为 KeyValuePair 是一种值类型(因此永远不能将其视为引用;我已在本地副本中为此添加了更好的消息).


At the current time this is sort of an unsupported scenario - at least, via the attributes it is unsupported; basically, the AsReference=true currently is referring to the KeyValuePair<int,A>, which doesn't really make sense since KeyValuePair<int,A> is a value-type (so this can never be treated as a reference; I've added a better message for that in my local copy).

因为KeyValuePair(默认情况下)作为一个元组,目前没有地方支持AsReference信息,但这是我想要更好地支持的场景,我将对此进行调查.

Because KeyValuePair<int,A> acts (by default) as a tuple, there is currently nowhere to support the AsReference information, but that is a scenario I would like to support better, and I will be investigating this.

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

There was also a bug that meant that AsReference on tuples (even reference-type tuples) was getting out-of-order, but I've fixed that locally; this was where the "changed" message came from.

理论上,我做这件事的工作量并不大;基本原理已经奏效,而且奇怪的是昨晚在 twitter 上也单独出现了 - 我猜字典指向一个对象"是一个非常常见的场景.猜测一下,我想我会添加一些属性来帮助描述这种情况,但实际上您现在可以使用几种不同的路线来绕过它:

In theory, the work for me to do this isn't huge; the fundamentals already work, and oddly enough it came up separately on twitter last night too - I guess "dictionary pointing to an object" is a very common scenario. At a guess, I imagince I'll add some atribute to help describe this situation, but you can actually hack around it at the moment using a couple of different routes:

1:手动配置KeyValuePair:

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

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

I don't like this much, because it exploits implementation details of KeyValuePair<,> (the private fields), and may not work between .NET versions. I would prefer to replace KeyValuePair<,> on the fly via a surrogate:

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

这配置了一些使用而不是 KeyValuePair(通过操作符转换).

This configures something to use instead of KeyValuePair<int,A> (converted via the operators).

在这两种情况下,Execute 只是:

In both of these, Execute is just:

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]);
}

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

I do, however, want to add direct support. The good thing about both of the above is that when I get time to do that, you just have to remove the custom configuration code.

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

For completeness, if your code is using Serializer.* methods, then rather than create / configure a new model, you should configure the default model:

RuntimeTypeModel.Default.Add(...); // etc

Serializer.* 基本上是 RuntimeTypeModel.Default.* 的捷径.

Serializer.* is basically a short-cut to RuntimeTypeModel.Default.*.

最后:你不应该每次调用都创建一个新的TypeModel;那会影响性能.您应该创建和配置一个模型实例,并大量重复使用它.或者只是使用默认模型.

Finally: you should not create a new TypeModel per call; that would hurt prerformance. You should create and configure one model instance, and re-use it lots. Or just use the default model.

这篇关于使用字典的 Protobuf-net 对象引用反序列化:一个引用跟踪对象在反序列化期间更改了引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆