我在使用protobuf-net尝试序列化/反序列化复杂对象图时遇到了一些问题。
我正在研究遗留应用程序,我们正在使用.Net Remoting将GUI客户端连接到C#服务。由于使用默认BinaryFormatter
的对象图的序列化大小,我们看到海外用户的表现不佳,客户端和服务器之间的带宽有限(1Mbit / s)会加剧这种情况。
作为一个快速的胜利,我想我已经整理了一个概念证明,通过实施ISerializable
使用protobuf-net来确定是否有任何性能提升。在我测试时,我遇到了一个问题,即没有维护对象引用。
我汇总了一个重新解决问题的例子。我期望Dictionary
(Items [1])和对象BA中的对象与我在AsReference=true
属性中指定ProtoMember
的对象相同。
使用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]);
}
班级定义
[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>();
}
}
答案 0 :(得分:4)
编辑:这应该从下一个版本开始工作,只需标记类型AsReferenceDefault
:
[ProtoContract(AsReferenceDefault=true)]
public class A
{
// ...
}
目前,这是一种不支持的方案 - 至少,通过属性不受支持;基本上,AsReference=true
目前指的是KeyValuePair<int,A>
,由于KeyValuePair<int,A>
是一个值类型,所以这并不合理(所以这永远不会是作为参考;我在本地副本中添加了更好的信息。
由于KeyValuePair<int,A>
(默认情况下)作为元组,因此目前无法支持AsReference
信息,但这是我希望更好地支持的方案,我将调查此事。
还有还一个错误,意味着元组(甚至引用类型元组)上的AsReference
无序,但我已经在本地修复了它;这就是“改变”消息的来源。
理论上,我这样做的工作并不多; 基础已经有效了,奇怪的是它昨晚也在Twitter上单独出现 - 我猜“指向对象的字典”是一种非常常见的情况。在猜测中,我想我会添加一些属性来帮助描述这种情况,但是你现在可以使用几种不同的路径来解决它:
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);
}
我不喜欢这么多,因为它利用了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);
}
}
这会配置使用而不是 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]);
}
但是,我确实希望增加直接支持。上述两个方面的好处是,当我有时间这样做时,您只需要删除自定义配置代码。
为了完整性,如果您的代码使用Serializer.*
方法,那么您应该配置默认模型,而不是创建/配置新模型:< / p>
RuntimeTypeModel.Default.Add(...); // etc
Serializer.*
基本上是RuntimeTypeModel.Default.*
的捷径。
最后:你不应该每次通话创建一个新的TypeModel
;这会伤害先行者。您应该创建并配置一个模型实例,并重复使用它。或者只使用默认模型。
答案 1 :(得分:2)
我已经设置了一个小测试,发现AsReferenceDefault属性不能按预期工作。
[ProtoContract(AsReferenceDefault = true)]
public class TEST
{
[ProtoMember(1018)]
public List<TEST> _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<TEST>(); ci.NameItemType = "ROOT_ITEM";
TEST ci_2 = new TEST(); ci_2._Items = new List<TEST>(); ci_2.NameItemType = "ITEM_02"; ci.AddItem(ci_2);
TEST ci_3 = new TEST(); ci_3._Items = new List<TEST>(); 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<TEST>(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 :(得分:0)
对于那些可能来这里遇到类似问题的人的更新:从版本2.3.0开始,不需要使用上面提到的Marc任何技巧。一切都像主题初学者想要的那样:
[TestClass]
public class UnitTest1
{
[TestMethod]
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;
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 A A { get; set; }
[ProtoMember( 2, AsReference = true )]
public Dictionary<int, A> Items { get; set; }
public B()
{
Items = new Dictionary<int, A>();
}
}