Как внедрить информацию о типе для целей де-сериализации с использованием protobuf-net? - PullRequest
3 голосов
/ 16 августа 2011

Я бы хотел иметь возможность сериализовать конкретные экземпляры IMessage таким образом, чтобы информация о типе сохранялась / встраивалась (подобно тому, что доступно, например, в Json.NET), чтобы при десериализации эта информация о типе могла использоваться материализовать эти конкретные случаи. Я хорошо знаю, что приведенные ниже методы де-сериализации не работают. Мы будем благодарны за любые рекомендации о том, как их изменить, чтобы они работали.

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).

1 Ответ

3 голосов
/ 16 августа 2011

Есть два способа приблизиться к этому;Мой наименьший предпочтительный вариант - использовать 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 должны быть кэшированы и использованы повторно;это потокобезопасно, поэтому может активно использоваться параллельно различными потоками и т. д.

...