How to use protobuf-net with immutable value types?

后端 未结 2 588
野趣味
野趣味 2021-02-13 16:04

Suppose I have an immutable value type like this:

[Serializable]
[DataContract]
public struct MyValueType : ISerializable
{
private readonly int _x;
private read         


        
相关标签:
2条回答
  • 2021-02-13 16:40

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

    0 讨论(0)
  • 2021-02-13 16:59

    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

    0 讨论(0)
提交回复
热议问题