5

假设我想序列化,然后使用 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;
}

...然后我无法序列化 adecimal并获得MyDecimal回报,也无法序列化 aMyDecimal并获得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# 进行通信,并且无法弄清楚十进制反序列化失败的原因。

4

1 回答 1

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>与 100% 兼容也是正确的Serialize<MyDecimalWrapper>,但为了您自己的理智,坚持简单的“始终序列化 DTO 实例”方法可能更容易,而不必考虑“这是一个 DTO?还是一个赤裸裸的价值?”


最后的想法:如果您使用互操作,我建议避免使用decimal,因为 protobuf 规范中没有定义,并且不同平台的“十进制”类型通常具有不同的含义。protobuf-net发明了一个含义,主要是为了允许 protobuf-net 往返(到自己)更广泛的 DTO,但是将那个值解析到任意平台可能会很尴尬。在跨decimal平台工作和使用. _ _doublefloatlongulongstring

于 2013-05-15T12:26:22.493 回答