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

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

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

作为一个快速的胜利,我想我已经整理了一个概念certificate,通过使用protobuf-net代替,通过实现ISerializable来确定是否有任何性能提升。 在我测试时,我遇到了一个问题,即没有维护对象引用。

我汇总了一个重新解决问题的例子。 我希望Dictionary (Items [1])中的对象和对象BA与我在ProtoMember属性中指定的AsReference=true相同。

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

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

测试

 [Test] public void AreObjectReferencesSameAfterDeserialization() { A a = new A(); B b = new B(); bA = a; b.Items.Add(1, a); Assert.AreSame(a, bA); Assert.AreSame(bA, b.Items[1]); B deserializedB; using (var stream = new MemoryStream()) { Serializer.Serialize(stream, b); stream.Seek(0, SeekOrigin.Begin); deserializedB = Serializer.Deserialize(stream); } Assert.AreSame(deserializedB.A, deserializedB.Items[1]); } 

类定义

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

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

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

目前,这是一种不受支持的情况 – 至少, 通过不受支持的属性 ; 基本上, AsReference=true 当前是指KeyValuePair ,这实际上没有意义,因为KeyValuePair是一个值类型(所以这永远不能被视为参考;我’我在本地副本中添加了更好的消息)。

因为KeyValuePair作为元组 (默认情况下)作为元组 ,目前无处可支持AsReference信息,但这是我想要更好地支持的场景,我将研究这个。

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

理论上,我这样做的工作并不多; 基本面已经有效,奇怪的是它昨晚也在Twitter上单独出现 – 我猜“指向一个对象的字典”是一种非常常见的情况。 猜测,我想我会添加一些属性来帮助描述这种情况,但是你现在可以使用几种不同的路径来解决它:

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 var type = model.Add(typeof(KeyValuePair), 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<,>

 [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 model[typeof(KeyValuePair)].SetSurrogate(typeof(RefPair)); Execute(model); } [ProtoContract] public struct RefPair { [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 (RefPair val) { return new KeyValuePair(val.Key, val.Value); } public static implicit operator RefPair (KeyValuePair val) { return new RefPair(val.Key, val.Value); } } 

这将配置要使用的内容而不是 KeyValuePair (通过运算符转换)。

在这两个中, Execute只是:

 private void Execute(TypeModel model) { A a = new A(); B b = new B(); bA = a; b.Items.Add(1, a); Assert.AreSame(a, bA); Assert.AreSame(bA, b.Items[1]); B deserializedB = (B)model.DeepClone(b); Assert.AreSame(deserializedB.A, deserializedB.Items[1]); } 

但是,我想增加直接支持。 上述两个方面的好处是,当我有时间这样做时,您只需要删除自定义配置代码。

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

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

Serializer.*基本上是RuntimeTypeModel.Default.*的快捷方式。

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

我设置了一个小测试,发现AsReferenceDefault属性不能按预期工作。

测试类:

 [ProtoContract(AsReferenceDefault = true)] public class TEST { [ProtoMember(1018)] public List _Items { get; set; } [ProtoMember(1001, AsReference = true)] public TEST Parent; [ProtoMember(1003)] public string NameItemType; public void AddItem(TEST Item) { _Items.Add(Item); Item.Parent = this; } public TEST() { } } 

测试代码:

  TEST ci = new TEST(); ci._Items = new List(); ci.NameItemType = "ROOT_ITEM"; TEST ci_2 = new TEST(); ci_2._Items = new List(); ci_2.NameItemType = "ITEM_02"; ci.AddItem(ci_2); TEST ci_3 = new TEST(); ci_3._Items = new List(); ci_3.NameItemType = "ITEM_03"; ci_2.AddItem(ci_3); // --> Confirm references. bool AreEqual = false; if (ci == ci_2.Parent) AreEqual = true; if (ci_2 == ci_3.Parent) AreEqual = true; // --> Serialize. byte[] buf; using (System.IO.MemoryStream ms = new System.IO.MemoryStream()) { ProtoBuf.Serializer.Serialize(ms, ci); buf = ms.ToArray(); } // --> Deserialize. using (System.IO.MemoryStream ms = new System.IO.MemoryStream(buf)) { ci = ProtoBuf.Serializer.Deserialize(ms); } // --> Confirm references. ci_2 = ci._Items[0]; ci_3 = ci_2._Items[0]; if (ci == ci_2.Parent) AreEqual = true; if (ci_2 == ci_3.Parent) // HERE IS WHERE IT FAILS! // THEY SHOULD BE EQUAL AFTER DESERIALIZATION! AreEqual = true; 

对于那些可能来这里遇到类似问题的人的更新:从版本2.3.0开始,不需要使用上面提到的任何Marc技巧。 一切都像Topic Starter想要的那样起作用:

 [TestClass] public class UnitTest1 { [TestMethod] public void AreObjectReferencesSameAfterDeserialization() { A a = new A(); B b = new B(); bA = a; b.Items.Add( 1, a ); Assert.AreSame( a, bA ); Assert.AreSame( bA, b.Items[ 1 ] ); B deserializedB; var model = TypeModel.Create(); using( var stream = new MemoryStream() ) { model.Serialize( stream, b ); stream.Seek( 0, SeekOrigin.Begin ); deserializedB = (B) model.Deserialize( stream, null, typeof(B) ); } Assert.AreSame( deserializedB.A, deserializedB.Items[ 1 ] ); } } [ProtoContract] public class A { } [ProtoContract] public class B { [ProtoMember( 1, AsReference = true )] public AA { get; set; } [ProtoMember( 2, AsReference = true )] public Dictionary Items { get; set; } public B() { Items = new Dictionary(); } }