如何使用 protobuf-net 嵌入类型信息以用于反/序列化目的? [英] How to embed type information for de-/serialization purposes using protobuf-net?

查看:42
本文介绍了如何使用 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆