2013-05-15 10 views
5

Sagen wir, ich serialisieren möchten, deserialisieren dann eine Dezimalzahl protobuf-net mit:Protobuf-net (de) Serialisierung von Dezimalstellen wirft, wenn benutzerdefinierte Dezimalzahl proto Vertrag mit (C#/C++ Interop)

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); 
} 

Es funktioniert gut . Protobuf-net intern verwendet die folgende Darstellung für Dezimalzahlen (vgl 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 
} 

Jetzt sagen, dass ich einen vermeintlich gleichwertigen proto Auftrag durch Code definieren:

[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; 
} 

... dann kann ich‘ t serialisieren Sie eine decimal und erhalten Sie eine MyDecimal zurück, noch eine MyDecimal serialisieren und erhalten Sie eine decimal zurück.

Von decimal-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); 
} 

Von MyDecimal-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); 
} 

Fehle ich etwas hier?

Ich arbeite an einer C++ - Anwendung, die mit einem C# One über Protokollpuffer kommunizieren muss und kann nicht herausfinden, warum dezimale Deserialisierungen fehlschlagen.

Antwort

3

Dies ist ein Randfall des "Ist es ein Objekt? Oder ein nackter Wert?". Sie können nicht nur serialisieren ein int, sagen wir, in Protobuf - Sie brauchen ein Wrapper-Objekt. Für nackte Werte gibt es daher vor, dass der Wert tatsächlich das Feld 1 eines hypothetischen Wrapperobjekts ist. Im Fall von decimal ist dies jedoch etwas knifflig - da decimal tatsächlich so codiert ist, als wäre es ein Objekt. So technischdecimal als nackter Wert geschrieben werden könnte ... aber: Es sieht aus wie es ist nicht (es wickelt es) - und ich bezweifle, es wäre eine gute Idee sein, dass in diesem Stadium zu korrigieren .

Grundsätzlich wird dies eine Menge Arbeit zuverlässiger, wenn man statt einen nackten Wert von Serialisierung serialisiert Sie ein Objekt dass einen Wert hat. Es wird auch arbeiten effizienter (protobuf-net sucht nach Typen, die es kennt, mit den nackten Werten sehr viel ein Fallback-Szenario). Zum Beispiel:

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

Wenn wir diese serialisiert werden, sie sind zu 100% untereinander austauschbar:

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 
} 

Eigentlich weil protobuf-net vorgibt es ein Objekt, ist es auch wahr, zu sagen, dass Serialize<decimal> wäre 100% kompatibel mit Serialize<MyDecimalWrapper>, aber für Ihre eigene Gesundheit ist es wahrscheinlich nur leichter zu einem einfachen "immer serialize eine DTO-Instanz" -Ansatz zu bleiben, anstatt zu denken "ist das ein DTO? oder ist es ein nackter Wert?"


Als letzter Gedanke: Wenn Sie Interop verwenden, würde ich vorschlagen, decimal zu vermeiden, da diese nicht in der protobuf-Spezifikation definiert ist, und verschiedene Plattformen haben oft eine andere Bedeutung ihrer‚dezimal‘-Typ. protobuf-net erfindet eine Bedeutung, hauptsächlich, um es protobuf-net zu ermöglichen, einen größeren Bereich von DTOs (zu sich selbst) zu umgehen, aber es kann schwierig sein, diesen Wert in eine beliebige Plattform zu zerlegen decimal, empfehle ich unter Berücksichtigung Dinge wie double/float, oder einige feste Präzision über long/ulong oder vielleicht sogar nur string.

+1

Danke für die Antwort. All dies macht Sinn. Einige Kommentare: 1. Wir senden keine nackten Dezimalzahlen, wir verwenden sie in DTO. 2. Ja, meine Probe zeigt nicht ganz das Problem, das ich hatte; Tatsächlich fand ich heraus, dass die Serialisierung/Deserialisierung eines DecimalWrapper, der eine Dezimalstelle zu/von einem MyDecimalWrapper mit einem MyDecimal enthielt, wie erwartet funktionierte. 3. In meinem Fall habe ich eine Dezimalstelle in C++, die als eine C# kompatible lo/hi/signscale ausgedrückt werden kann; das Protobuf-Netz-Dezimalsystem sah wie ein guter Vertrag aus. –

+1

4. Das Problem scheint durch die Tatsache verursacht zu werden, dass bcl.proto decimal mit einer 'sint32' SignScale definiert wurde, und dass protobuf-net ein' uint32' zum/vom Stream schrieb/las. Wenn die Serialisierung in C++ erfolgte (Schreiben von 'sint32'), dann erzeugt die Art, wie protobuf-net sie liest, einen ungültigen Wert. Ich bin nicht einmal sicher, wie man 'sint32' Werte in C# behandelt. –

+0

@RomainVerdier ah, 'sint32' vs' uint32' ... verdammt, das ist ein Schmerz. Die Zick-Zack-Kodierung ist einfach genug, aber wenn ich den Vertrag und Code verschiedene Dinge sagen lasse, dann ist das ärgerlich –