How to use protobuf-net with immutable value types?

Which version of protobuf-net are you using? If you are the latest v2 build, it should cope with this automatically. In case I haven't deployed this code yet, I'll update the download areas in a moment, but essentially if your type is unadorned (no attributes), it will detect the common "tuple" patten you are using, and decide (from the constructor) that x (constructor parameter)/X (property) is field 1, and z/Z is field 2.

Another approach is to mark the fields:

[ProtoMember(1)]
private readonly int _x;

[ProtoMember(2)]
private readonly int _z;

(or alternatively [DataMember(Order=n)] on the fields)

which should work, depending on the trust level. What I haven't done yet is generalise the constructor code to attributed scenarios. That isn't hard, but I wanted to push the basic case first, then evolve it.

I've added the following two samples/tests with full code here:

    [Test]
    public void RoundTripImmutableTypeAsTuple()
    {
        using(var ms = new MemoryStream())
        {
            var val = new MyValueTypeAsTuple(123, 456);
            Serializer.Serialize(ms, val);
            ms.Position = 0;
            var clone = Serializer.Deserialize<MyValueTypeAsTuple>(ms);
            Assert.AreEqual(123, clone.X);
            Assert.AreEqual(456, clone.Z);
        }
    }
    [Test]
    public void RoundTripImmutableTypeViaFields()
    {
        using (var ms = new MemoryStream())
        {
            var val = new MyValueTypeViaFields(123, 456);
            Serializer.Serialize(ms, val);
            ms.Position = 0;
            var clone = Serializer.Deserialize<MyValueTypeViaFields>(ms);
            Assert.AreEqual(123, clone.X);
            Assert.AreEqual(456, clone.Z);
        }
    }

Also:

it turns out that the Serialize method only allows reference types

yes, that was a design limitation of v1 that related to the boxing model etc; this no longer applies with v2.

Also, note that protobuf-net doesn't itself consume ISerializable (although it can be used to implement ISerializable).


The selected answer didn't work for me since the link is broken and I cannot see the MyValueTypeViaFields code.

In any case I have had the same exception No parameterless constructor found for my class:

[ProtoContract]
public class FakeSimpleEvent
    : IPersistableEvent
{
    [ProtoMember(1)]
    public Guid AggregateId { get; }
    [ProtoMember(2)]
    public string Value { get; }
    public FakeSimpleEvent(Guid aggregateId, string value)
    {
        AggregateId = aggregateId;
        Value = value;
    }
}

when deserializing it with the following code:

public class BinarySerializationService
    : IBinarySerializationService
{
    public byte[] ToBytes(object obj)
    {
        if (obj == null) throw new ArgumentNullException(nameof(obj));
        using (var memoryStream = new MemoryStream())
        {
            Serializer.Serialize(memoryStream, obj);
            var bytes = memoryStream.ToArray();
            return bytes;
        }
    }

    public TType FromBytes<TType>(byte[] bytes)
        where TType : class
    {
        if (bytes == null) throw new ArgumentNullException(nameof(bytes));
        var type = typeof(TType);
        var result = FromBytes(bytes, type);
        return (TType)result;
    }

    public object FromBytes(byte[] bytes, Type type)
    {
        if (bytes == null) throw new ArgumentNullException(nameof(bytes));
        int length = bytes.Length;
        using (var memoryStream = new MemoryStream())
        {
            memoryStream.Write(bytes, 0, length);
            memoryStream.Seek(0, SeekOrigin.Begin);
            var obj = Serializer.Deserialize(type, memoryStream);
            return obj;
        }
    }
}

being called like var dataObject = (IPersistableEvent)_binarySerializationService.FromBytes(data, eventType);

My message class FakeSimpleEvent has indeed parameterless constructor because I want it immutable.

I am using protobuf-net 2.4.0 and I can confirm that it supports complex constructors and immutable message classes. Simply use the following decorator

[ProtoContract(SkipConstructor = true)]

If true, the constructor for the type is bypassed during deserialization, meaning any field initializers or other initialization code is skipped.

UPDATE 1: (20 June 2019) I don't like polluting my classes with attributes that belong to protobuffer because the domain model should be technology-agnostic (other than dotnet framework's types of course)

So for using protobuf-net with message classes without attributes and without parameterless constructor (i.e: immutable) you can have the following:

public class FakeSimpleEvent
    : IPersistableEvent
{
    public Guid AggregateId { get; }
    public string Value { get; }
    public FakeSimpleEvent(Guid aggregateId, string value)
    {
        AggregateId = aggregateId;
        Value = value;
    }
}

and then configure protobuf with the following for this class.

var fakeSimpleEvent = RuntimeTypeModel.Default.Add(typeof(FakeSimpleEvent), false);
fakeSimpleEvent.Add(1, nameof(FakeSimpleEvent.AggregateId));
fakeSimpleEvent.Add(2, nameof(FakeSimpleEvent.Value));
fakeSimpleEvent.UseConstructor = false;

This would be the equivalent to my previous answer but much cleaner.

PS: Don't mind the IPersistableEvent. It's irrelevant for the example, just a marker interface I use somewhere else