使用自定义十进制原型契约(C#/ C ++互操作)时,使用Protobuf-net(de)序列化小数

时间:2013-05-15 08:45:58

标签: c# c++ decimal protocol-buffers protobuf-net

假设我想序列化,然后使用protobuf-net反序列化小数:

const decimal originalDecimal = 1.6641007661819458m;
using (var memoryStream = new MemoryStream())
{
    Serializer.Serialize(memoryStream, originalDecimal);
    memoryStream.Position = 0;
    var deserializedDecimal = Serializer.Deserialize<decimal>(memoryStream);
    Assert.AreEqual(originalDecimal, deserializedDecimal);
}

工作正常。 Protobuf-net内部使用以下小数表示(参见Bcl.proto):

message Decimal {
  optional uint64 lo = 1; // the first 64 bits of the underlying value
  optional uint32 hi = 2; // the last 32 bis of the underlying value
  optional sint32 signScale = 3; // the number of decimal digits, and the sign
}

现在说我用代码定义了一个假定的等价原型合约:

[ProtoContract]
public class MyDecimal
{
    [ProtoMember(1, IsRequired = false)]
    public ulong Lo;

    [ProtoMember(2, IsRequired = false)]
    public uint Hi;

    [ProtoMember(3, IsRequired = false)]
    public int SignScale;
}

...然后我无法序列化decimal并获得MyDecimal,也无法序列化MyDecimal并获得decimal

decimalMyDecimal

const decimal originalDecimal = 1.6641007661819458m;
using (var memoryStream = new MemoryStream())
{
    Serializer.Serialize(memoryStream, originalDecimal);
    memoryStream.Position = 0;

    // following line throws a Invalid wire-type ProtoException
    Serializer.Deserialize<MyDecimal>(memoryStream);
}

MyDecimaldecimal

var myDecimal = new MyDecimal
{
    Lo = 0x003b1ee886632642,
    Hi = 0x00000000,
    SignScale = 0x00000020,
};

using (var memoryStream = new MemoryStream())
{
    Serializer.Serialize(memoryStream, myDecimal);
    memoryStream.Position = 0;

    // following line throws a Invalid wire-type ProtoException
    Serializer.Deserialize<decimal>(memoryStream);
}

我在这里遗漏了什么吗?

我正在研究一个需要通过协议缓冲区与C#进行通信的C ++应用程序,并且无法确定十进制反序列化失败的原因。

1 个答案:

答案 0 :(得分:3)

这是“它是一个对象还是一个裸值?”的边缘情况。你不能只是序列化一个int,比如说,在protobuf中 - 你需要一个包装器对象。因此,对于裸值,它假装该值实际上是假设包装器对象的字段1。但是,在decimal的情况下,这有点棘手 - 因为decimal实际上被编码为好像它是一个对象。所以技术上 decimal 可以写成一个裸值...但是:它看起来像不是(它是包装它) - 我怀疑在这个阶段纠正它是个好主意。

基本上,如果不是序列化裸值,而是序列化对象 具有值,那么这将更加可靠。它更有效地工作(protobuf-net寻找它所知道的类型,裸值非常多的回退场景)。例如:

[ProtoContract]
class DecimalWrapper {
    [ProtoMember(1)]
    public decimal Value { get; set; }
}
[ProtoContract]
class MyDecimalWrapper {
    [ProtoMember(1)]
    public MyDecimal Value { get; set; }
}

如果我们序列化这些,它们是100%可互换的:

const decimal originalDecimal = 1.6641007661819458m;
using (var memoryStream = new MemoryStream())
{
    var obj = new DecimalWrapper { Value = originalDecimal };
    Serializer.Serialize(memoryStream, obj);
    // or, as it happens (see text) - this is equal to
    // Serializer.Serialize(memoryStream, originalDecimal);

    memoryStream.Position = 0;
    var obj2 = Serializer.Deserialize<MyDecimalWrapper>(memoryStream);
    Console.WriteLine("{0}, {1}, {2}",
        obj2.Value.Lo, obj2.Value.Hi, obj2.Value.SignScale);
    // ^^^ 16641007661819458, 0, 32

    memoryStream.SetLength(0);
    Serializer.Serialize(memoryStream, obj2);
    memoryStream.Position = 0;
    var obj3 = Serializer.Deserialize<DecimalWrapper>(memoryStream);

    bool eq = obj3.Value == obj.Value; // True
}

实际上,因为protobuf-net 假装有一个对象,所以Serialize<decimal>Serialize<MyDecimalWrapper> 100%兼容也是如此,但对于你自己理智,它可能只是更容易坚持一个简单的“总是序列化DTO实例”的方法,而不是必须认为“这是一个DTO?还是一个裸体价值?”


最后一点想法:如果你使用互操作,我建议避免使用decimal,因为protobuf规范中没有定义,并且不同的平台通常具有不同的“十进制”类型含义。 protobuf-net 发明一个含义,主要是为了让protobuf-net能够往返(自身)更广泛的DTO,但将该值解析为任意平台可能会很尴尬。在跨平台工作并使用decimal时,我建议考虑{/ 1}} / double之类的内容,或通过float / {{ 1}},或者甚至只是long