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


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

[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/)序列化器我得到这个错误:

无法对属性应用更改   ConsoleApplication.Program + MyValueType.X

如果我将setter应用于标有DataMember属性的属性,它将起作用,但它会打破这种值类型的不变性,这对我们来说是不可取的。

有谁知道我需要做些什么才能让它发挥作用?我注意到ProtoBu.Serializer.Serialize方法有一个重载,它接受一个SerializationInfo和一个StreamingContext,但是我没有在实现ISerializable接口的上下文之外使用它们,所以任何关于如何使用它们的代码示例这个背景将非常感谢!

谢谢,

编辑:所以我挖了一些旧的MSDN文章,并更好地了解了SerializationInfo和StreamingContext的使用位置和方式,但是当我尝试这样做时:

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

原来那个 Serialize<T> 方法只允许引用类型,有特殊原因吗?考虑到我能够序列化通过引用类型公开的值类型,这似乎有点奇怪。


9645
2017-08-19 11:25


起源

抱歉延迟 - 忙碌的周末 - Marc Gravell♦


答案:


您使用的是哪个版本的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)。


11
2017-08-21 19:34



+1很棒的答案,几乎是一个帖子:-) - Ron Klein
嗨Marc,刚下载了你发布的新二进制文件并且它工作正常,我还检查了使用RuntimeTypeModel实例进行序列化/反序列化,它们也可以工作。 - theburningmonk
但我很好奇,至于为什么RuntiemTypeModel.Deserialize方法需要一个实例来修改它无论如何返回修改后的对象。在不可变值类型的情况下,这意味着您最终在以下行中执行某些操作:var clone =(MyValueType)FormatterServices.GetUninitializedObject(typeof(MyValueType)); clone =(MyValueType)runtimeTypeModel.Deserialize(memStream,clone,typeof(MyValueType));这感觉不太对劲 - theburningmonk
@theburningmonk在这种情况下你可以通过 null 在;这里的要点是API旨在支持“合并”以将值应用于 现有 目的。如果(通常)您希望序列化程序创建对象,只需传递即可 null。 - Marc Gravell♦
确实,@theburningmonk,如果使用protobuf-net来实现则非常重要 ISerializable - Marc Gravell♦