Say I want to serialize, then deserialize a decimal using 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);
}
It works fine. Protobuf-net internally uses the following representation for decimals (cf. 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
}
Now say that I define a supposedly equivalent proto contract by code:
[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;
}
...then I can't serialize a decimal
and get a MyDecimal
back, nor serialize a MyDecimal
and get a decimal
back.
From decimal
to MyDecimal
:
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);
}
From MyDecimal
to decimal
:
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);
}
Am I missing something here?
I'm working on a C++ application which needs to communicate with a C# one through protocol buffers and can't figure why decimal deserializations fail.
This is an edge case of the "is it an object? or a naked value?". You can't just serialize an int
, say, in protobuf - you need a wrapper object. For naked values, therefore, it pretends that the value is actually field 1 of a hypothetical wrapper object. In the case of decimal
, though, this is a bit tricky - since decimal
is actually encoded as though it were an object. So technically decimal
could be written as a naked value... but: it looks like it isn't (it is wrapping it) - and I doubt it would be a good idea to rectify that at this stage.
Basically, this will work a lot more reliably if instead of serializing a naked value, you serialize an object that has a value. It will also work more efficiently (protobuf-net looks for types it knows about, with the naked values very much a fallback scenario). For example:
[ProtoContract]
class DecimalWrapper {
[ProtoMember(1)]
public decimal Value { get; set; }
}
[ProtoContract]
class MyDecimalWrapper {
[ProtoMember(1)]
public MyDecimal Value { get; set; }
}
If we serialize these, they are 100% interchangeable:
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
}
Actually, because protobuf-net pretends there is an object, it is also true to say that Serialize<decimal>
would be 100% compatible with Serialize<MyDecimalWrapper>
, but for your own sanity it is probably just easier to stick to a simple "always serialize a DTO instance" approach, rather than having to think "is this a DTO? or is it a naked value?"
As a final thought: if you are using interop, I would suggest avoiding decimal
, since that is not defined in the protobuf specification, and different platforms often have a different meaning of their "decimal" type. protobuf-net invents a meaning, mainly to allow protobuf-net to round-trip (to itself) a wider range of DTOs, but it may be awkward to parse that value into an arbitrary platform. When working cross-platform and using decimal
, I recommend considering things like double
/float
, or some fixed precision via long
/ulong
, or maybe even just string
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With