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