5
votes

Does anybody know what is the right way to set up a ProtoContract for an Interface?

I get the following exception "The type cannot be changed once a serializer has been generated" using only attributes.

Code used:

    [ProtoContract]
    public class Lesson5TestClass2 : ILesson5TestInteface1
    {
        [ProtoMember(1)]
        public string Name { get; set; }
        [ProtoMember(2)]
        public string Phone { get; set; }
    }

    [ProtoContract]
    [ProtoInclude(1000, typeof(Lesson5TestClass2))]
    public interface ILesson5TestInteface1
    {
        [ProtoMember(1)]
        string Name { get; set; }
        [ProtoMember(2)]
        string Phone { get; set; }
    }

I'm able to deserialize only if I add the following setting:

  RuntimeTypeModel.Default.Add(typeof (ILesson5TestInteface1), true)
      .AddSubType(50, typeof(Lesson5TestClass2));

I'd really love to configure this using only attributes.

I'm using protobuf-net r470 from NuGet.

BTW: This example is from a set of "Lessons through tests" showing how to do serialization with protobuf-net for my coworkers.

Thanks for reading :)

2
Why do you need interfaces for DTO´s? - jgauffin
@jgauffin: I'm just writting some code to understand protobuf-net capabilities. Lesson 5 is all about interfaces. :) - graffic

2 Answers

1
votes

Interesting; yes, it looks like something is up there. However, it does work when exposed as a member, i.e.

[ProtoContract]
class Wrapper
{
    [ProtoMember(1)]
    public ILesson5TestInteface1 Content { get; set; }
}
static class Program
{
    static void Main()
    {
        Wrapper obj = new Wrapper
        {
            Content = new Lesson5TestClass2()
        }, clone;
        using(var ms = new MemoryStream())
        {
            Serializer.Serialize(ms, obj);
            ms.Position = 0;
            clone = Serializer.Deserialize<Wrapper>(ms);
        }
        // here clone.Content *is* a Lesson5TestClass2 instance
    }
}

I will have to look to see what is up with interface support as the root object.

0
votes

Simplest solution that worked for me is to let Protobuf know about the interface before serializing the actual instance.

var obj = new Lesson5TestClass2();
using var ms = new MemoryStream();

// vvv - Add this line
Serializer.PrepareSerializer<ILesson5TestInteface1>();

Serializer.Serialize(ms, obj);
ms.Position = 0;
var clone = Serializer.Deserialize<ILesson5TestInteface1>(ms);

I guess that call makes Protobuf serialize Lesson5TestClass2 as instance of ILesson5TestInteface1 which it doesn't do otherwise, because it has no chance to know about ILesson5TestInteface1.

This is probably what also happens if you use wrapper class as suggested by the other answer (then Protobuf knows about the interface because it's a type of a property it inspects before serialization).

I'd really love to configure this using only attributes.

This solution is almost what you want, but still needs PrepareSerializer to be called once per interface (which is better than RuntimeTypeModel.Default.Add as that's once per implementation). However, you can easily create a helper method to call PrepareSerializer + Serialize combo every time (there will be no performance penalty as it's cached) and it should work just fine, no extra configuration needed.