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

Rom*_*ier 5 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);
}
Run Code Online (Sandbox Code Playgroud)

它工作正常.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
}
Run Code Online (Sandbox Code Playgroud)

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

[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;
}
Run Code Online (Sandbox Code Playgroud)

...然后我不能序列化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);
}
Run Code Online (Sandbox Code Playgroud)

从: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);
}
Run Code Online (Sandbox Code Playgroud)

我在这里错过了什么吗?

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

Mar*_*ell 4

这是“它是一个对象吗?还是一个裸值?”的边缘情况。你不能序列化一个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; }
}
Run Code Online (Sandbox Code Playgroud)

如果我们将它们序列化,它们是 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
}
Run Code Online (Sandbox Code Playgroud)

实际上,因为 protobuf-net假装存在一个对象,所以说它Serialize<decimal>与 100% 兼容也是正确的,但为了您自己的理智,坚持简单的“始终序列化 DTO 实例”方法Serialize<MyDecimalWrapper>可能更容易,而不必思考“这是一个 DTO?还是一个赤裸裸的价值?”


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