如何使用 protobuf-net 嵌入类型信息以用于反/序列化目的? [英] How to embed type information for de-/serialization purposes using protobuf-net?
问题描述
我希望能够以保留/嵌入类型信息的方式序列化 IMessage 的具体实例(类似于 Json.NET 中可用的内容),以便在反序列化时可以使用类型信息实现这些具体实例.我很清楚下面的反/序列化方法不起作用.任何有关如何更改它们以使它们起作用的指导将不胜感激.
I'd like to be able to serialize concrete instances of IMessage in such a way that the type information is retained/embedded (akin to what's available in e.g. Json.NET), so that upon deserialization that type information can be used to materialize those concrete instances. I'm well aware that the de-/serialization methods below don't work. Any guidance would be appreciated on how to change them so they do work.
public interface IMessage {}
public interface IEvent : IMessage {}
[ProtoContract]
public class DogBarkedEvent : IEvent {
[ProtoMember(0)]
public string NameOfDog { get; set; }
[ProtoMember(1)]
public int Times { get; set; }
}
//Somewhere in a class far, far away
public byte[] Serialize(IMessage message) {
using(var stream = new MemoryStream()) {
ProtoBuf.Serializer.Serialize<IMessage>(stream, message);
return stream.ToArray();
}
}
public IMessage Deserialize(byte[] data) {
using(var stream = new MemoryStream(data)) {
return ProtoBuf.Serializer.Deserialize<IMessage>(stream);
}
}
说明一点:序列化事件被写入持久性.在阅读它们时,使用带有泛型参数的反序列化方法不是一个可行的选择(最好的方法是将类型信息指定为常规参数或使用通用协定,在本例中为 IMessage).
To shed a little light: The serialized events get written to persistence. When reading them, usage of a deserialization method with a generic argument is not a viable option (the best that can be done is specifying the type information as a regular parameter or using the common contract, IMessage in this case).
推荐答案
有两种方法可以解决这个问题;我最不喜欢的首选是使用DynamicType=true
- 这更昂贵并且限制了可移植性/版本控制,但不要求了解数据前期.我的首选选项是为每个接口声明一个固定标识符,使其能够识别数据本身.如下所示.
There are two ways of approaching this; my least preferred option is to use DynamicType=true
- this is more expensive and limits portability/versioning, but places no demands on knowing the data up-front. My preferred option is to declare a fixed identifier per interface, allowing it to recognise the data itself. This is shown below.
关于信息,DontAskWrapper
是因为Serialize()
使用了GetType()
;这意味着它不会发现接口底座.我怀疑我可以改进它,但这适用于今天的 v2:
For info, DontAskWrapper
is because Serialize()
uses GetType()
; which means it won't spot the interface base. I suspect I can improve that, but this works for today on v2:
using System.Diagnostics;
using System.IO;
using NUnit.Framework;
using ProtoBuf;
using ProtoBuf.Meta;
namespace Examples.Issues
{
[TestFixture]
public class SO7078615
{
[ProtoContract] // treat the interface as a contract
// since protobuf-net *by default* doesn't know about type metadata, need to use some clue
[ProtoInclude(1, typeof(DogBarkedEvent))]
// other concrete messages here; note these can also be defined at runtime - nothing *needs*
// to use attributes
public interface IMessage { }
public interface IEvent : IMessage { }
[ProtoContract] // removed (InferTagFromName = true) - since you are already qualifying your tags
public class DogBarkedEvent : IEvent
{
[ProtoMember(1)] // .proto tags are 1-based; blame google ;p
public string NameOfDog { get; set; }
[ProtoMember(2)]
public int Times { get; set; }
}
[ProtoContract]
class DontAskWrapper
{
[ProtoMember(1)]
public IMessage Message { get; set; }
}
[Test]
public void RoundTripAnUnknownMessage()
{
IMessage msg = new DogBarkedEvent
{
NameOfDog = "Woofy", Times = 5
}, copy;
var model = TypeModel.Create(); // could also use the default model, but
using(var ms = new MemoryStream()) // separation makes life easy for my tests
{
var tmp = new DontAskWrapper {Message = msg};
model.Serialize(ms, tmp);
ms.Position = 0;
string hex = Program.GetByteString(ms.ToArray());
Debug.WriteLine(hex);
var wrapper = (DontAskWrapper)model.Deserialize(ms, null, typeof(DontAskWrapper));
copy = wrapper.Message;
}
// check the data is all there
Assert.IsInstanceOfType(typeof(DogBarkedEvent), copy);
var typed = (DogBarkedEvent)copy;
var orig = (DogBarkedEvent)msg;
Assert.AreEqual(orig.Times, typed.Times);
Assert.AreEqual(orig.NameOfDog, typed.NameOfDog);
}
}
}
<小时>
这里没有属性也是一样:
And here's the same thing without attributes:
public interface IMessage { }
public interface IEvent : IMessage { }
public class DogBarkedEvent : IEvent
{
public string NameOfDog { get; set; }
public int Times { get; set; }
}
class DontAskWrapper
{
public IMessage Message { get; set; }
}
[Test]
public void RoundTripAnUnknownMessage()
{
IMessage msg = new DogBarkedEvent
{
NameOfDog = "Woofy",
Times = 5
}, copy;
var model = TypeModel.Create();
model.Add(typeof (DogBarkedEvent), false).Add("NameOfDog", "Times");
model.Add(typeof (IMessage), false).AddSubType(1, typeof (DogBarkedEvent));
model.Add(typeof (DontAskWrapper), false).Add("Message");
using (var ms = new MemoryStream())
{
var tmp = new DontAskWrapper { Message = msg };
model.Serialize(ms, tmp);
ms.Position = 0;
string hex = Program.GetByteString(ms.ToArray());
Debug.WriteLine(hex);
var wrapper = (DontAskWrapper)model.Deserialize(ms, null, typeof(DontAskWrapper));
copy = wrapper.Message;
}
// check the data is all there
Assert.IsInstanceOfType(typeof(DogBarkedEvent), copy);
var typed = (DogBarkedEvent)copy;
var orig = (DogBarkedEvent)msg;
Assert.AreEqual(orig.Times, typed.Times);
Assert.AreEqual(orig.NameOfDog, typed.NameOfDog);
}
请注意,在两种情况下,TypeModel
应该被缓存并重新使用;它是线程安全的,因此可以被不同的线程等积极地并行使用.
Note that in both cases the TypeModel
should be cached and re-used; it is thread-safe, so can be aggressively used in parallel by different threads, etc.
这篇关于如何使用 protobuf-net 嵌入类型信息以用于反/序列化目的?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!