如何使用protobuf-net嵌入类型信息以进行去序列化/序列化?

我希望能够以这样的方式序列化IMessage的具体实例,以便保留/嵌入类型信息(类似于例如Json.NET中可用的类型),以便在反序列化时可以使用类型信息来实现那些类型信息。具体实例。 我很清楚下面的de / / serialization方法不起作用。 如何更改它们以便它们起作用,我们将不胜感激。

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(stream, message); return stream.ToArray(); } } public IMessage Deserialize(byte[] data) { using(var stream = new MemoryStream(data)) { return ProtoBuf.Serializer.Deserialize(stream); } } 

稍微阐明:序列化事件被写入持久性。 读取它们时,使用带有generics参数的反序列化方法不是一个可行的选项(可以做的最好的事情是将类型信息指定为常规参数或使用常见协定,在本例中为IMessage)。

有两种方法可以解决这个问题; 我最不喜欢的选择是使用DynamicType=true – 这是更昂贵的并且限制了可移植性/版本控制,但没有要求预先知道数据。 我首选的选择是为每个接口声明一个固定的标识符,允许它识别数据本身。 如下所示。

有关信息, DontAskWrapper是因为Serialize()使用GetType() ; 这意味着它不会发现接口基础。 我怀疑我可以改进它,但今天在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); } } } 

没有属性,这是同样的事情:

 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应该缓存并重新使用TypeModel ; 它是线程安全的,因此可以通过不同的线程等方式并行使用。