如何使用具有不可变值类型的protobuf-net

本文关键字:类型 protobuf-net 不可变 何使用 | 更新日期: 2023-09-27 18:06:11

假设我有一个这样的不可变值类型:

[Serializable]
[DataContract]
public struct MyValueType : ISerializable
{
private readonly int _x;
private readonly int _z;
public MyValueType(int x, int z)
    : this()
{
    _x = x;
    _z = z;
}
// this constructor is used for deserialization
public MyValueType(SerializationInfo info, StreamingContext text)
    : this()
{
    _x = info.GetInt32("X");
    _z = info.GetInt32("Z");
}
[DataMember(Order = 1)]
public int X
{
    get { return _x; }
}
[DataMember(Order = 2)]
public int Z
{
    get { return _z; }
}
public static bool operator ==(MyValueType a, MyValueType b)
{
    return a.Equals(b);
}
public static bool operator !=(MyValueType a, MyValueType b)
{
    return !(a == b);
}
public override bool Equals(object other)
{
    if (!(other is MyValueType))
    {
        return false;
    }
    return Equals((MyValueType)other);
}
public bool Equals(MyValueType other)
{
    return X == other.X && Z == other.Z;
}
public override int GetHashCode()
{
    unchecked
    {
        return (X * 397) ^ Z;
    }
}
// this method is called during serialization
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
    info.AddValue("X", X);
    info.AddValue("Z", Z);
}
public override string ToString()
{
    return string.Format("[{0}, {1}]", X, Z);
}
}

它与BinaryFormatter或DataContractSerializer一起工作,但当我尝试使用protobuf-net (http://code.google.com/p/protobuf-net/) serializer时,我得到这个错误:

不能对属性应用更改ConsoleApplication.Program + MyValueType.X

如果我对带有DataMember属性的属性应用setter,它将工作,但它会破坏该值类型的不可变性,这对我们来说是不可取的。

有谁知道我需要做什么才能让它工作吗?我注意到protobus . serializer . serialize方法有一个过载,它接受一个SerializationInfo和一个StreamingContext,但我没有在实现isserializable接口的上下文中使用它们,所以任何关于如何在这种上下文中使用它们的代码示例将非常感谢!

谢谢,

编辑:所以我挖了一些旧的MSDN文章,并得到了一个更好的理解在哪里和如何SerializationInfo和StreamingContext是使用的,但当我试图这样做:

var serializationInfo = new SerializationInfo(
    typeof(MyValueType), new FormatterConverter());
ProtoBuf.Serializer.Serialize(serializationInfo, valueType);

显示Serialize<T>方法只允许引用类型,这有什么特别的原因吗?考虑到我能够序列化通过引用类型暴露的值类型,这似乎有点奇怪。

如何使用具有不可变值类型的protobuf-net

您使用的是哪个版本的protobuf-net ?如果您是最新的v2版本,它应该自动处理这个问题。如果我还没有部署这段代码,我会马上更新下载区域,但本质上,如果你的类型是未修饰的(没有属性),它将检测你正在使用的常见"元组"模式,并决定(从构造函数)x(构造函数参数)/X(属性)是字段1,z/Z是字段2。

另一种方法是标记字段:
[ProtoMember(1)]
private readonly int _x;
[ProtoMember(2)]
private readonly int _z;

(或[DataMember(Order=n)]字段)

应该工作,这取决于信任级别。我还没有做的是将构造函数代码泛化到属性场景。这并不难,但我想先推动基本情况,然后发展它。

我在这里添加了以下两个完整代码的示例/测试:

    [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);
        }
    }

:

, Serialize方法只允许引用类型

是的,这是v1的设计限制,与装箱模型等有关;这不再适用于v2。

另外,请注意protobuf-net本身并不使用ISerializable(尽管它可以用来实现 ISerializable)。

选择的答案不适合我,因为链接断开了,我无法看到MyValueTypeViaFields代码。

在任何情况下,我有相同的异常No parameterless constructor found为我的类:

[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;
    }
}

,使用以下代码反序列化

:
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;
        }
    }
}

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

一样被调用

我的消息类FakeSimpleEvent确实有无参数构造函数,因为我希望它不可变。

我使用protobuf-net 2.4.0,我可以确认它支持复杂的构造函数和不可变的消息类。只需使用以下装饰符

[ProtoContract(SkipConstructor = true)]

如果为true,则在反序列化,意思是任何字段初始化器或其他初始化代码被跳过。

UPDATE 1:(2019年6月20日)我不喜欢用属于protobuffer的属性污染我的类,因为领域模型应该是与技术无关的(当然,dotnet框架的类型除外)

所以使用protobuf-net的消息类没有属性和无参数的构造函数(即:不可变的),你可以有以下:

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

,然后为这个类配置protobuf。

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

这将相当于我之前的答案,但更干净。

PS:不要介意IPersistableEvent。它与示例无关,只是我在其他地方使用的标记界面