我希望能够以保留/嵌入类型信息的方式序列化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<IMessage>(stream, message);
return stream.ToArray();
}
}
public IMessage Deserialize(byte[] data) {
using(var stream = new MemoryStream(data)) {
return ProtoBuf.Serializer.Deserialize<IMessage>(stream);
}
}
略微说明:序列化事件被写入持久性。在阅读它们时,使用带有泛型参数的反序列化方法不是一个可行的选择(可以做的最好的方法是将类型信息指定为常规参数或使用通用契约,在这种情况下为IMessage。)
答案 0 :(得分:3)
有两种方法可以解决这个问题;我的最少首选选项是使用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
应该被缓存并重新使用;它是线程安全的,因此可以通过不同的线程等方式并行使用。