我创建了一个自定义(readonly)结构来封装小数。我在任何地方使用结构,包括各种编程语言使用的面向公众的API,因此希望避免暴露十进制数据类型。
这显示了结构的相关部分:
[ProtoContract(SkipConstructor = false, ImplicitFields=ImplicitFields.None)]
public readonly struct Amount
{
[ProtoIgnore]
public const decimal Scale = 100000m;
[ProtoIgnore]
public decimal Value { get; }
[ProtoMember(1)]
public long ScaledValue { get; }
public Amount(decimal value)
{
Value = value;
ScaledValue = checked((long)(value * Scale).Round(0));
}
public Amount(long scaledValue)
{
Value = scaledValue / Scale;
ScaledValue = scaledValue;
}
public static Amount CreateFrom(long scaledValue) => new Amount(scaledValue);
}
我遇到的问题是,尽管ProtoContract上的SkipConstructor = false,但在反序列化期间不会调用ctor,导致只能正确初始化ScaledValue属性。
我不能使用ProtoAfterDeserialization方法来设置Value属性,因为该结构是只读的。
我试图在创建对象时为protobuf-net配置自定义工厂方法,方法是:
var createFrom = typeof(Amount).GetMethod("CreateFrom", BindingFlags.Public | BindingFlags.Static, null, new[] { typeof(long) }, null);
RuntimeTypeModel.Default[typeof(Amount)].SetFactory(createFrom);
但这总是会导致“InvalidOperationException:由于对象的当前状态,操作无效。”我已经验证找到了CreateFrom方法(所以传入一个有效的MethodInfo对象)。
关于如何使这项工作的任何想法?
struct
,特别是readonly struct
,我计划在v3中解决更多问题,该计划有新的序列化程序API。在过渡期间,它不是一个处理得很好的场景,但你最好的选择可能是“代理人” - 这意味着序列化程序在很大程度上忽略了Amount
,使用其他更加序列化的东西代替它。这也意味着您可以从Amount
中删除任何序列化程序属性或API:
using ProtoBuf;
using ProtoBuf.Meta;
static class P
{
static void Main()
{
// only need to do this once, *before*
// serializing/deserialing anything
RuntimeTypeModel.Default.Add(typeof(Amount), false)
.SetSurrogate(typeof(AmountSurrogate));
// test it works
var obj = new Foo { Amount = new Amount(123.45M) };
var clone = Serializer.DeepClone(obj);
System.Console.WriteLine(clone.Amount.Value);
}
}
[ProtoContract]
public class Foo
{
[ProtoMember(1)]
public Amount Amount { get; set; }
}
[ProtoContract]
struct AmountSurrogate
{ // a nice simple type for serialization
[ProtoMember(1)]
public long Value { get; set; }
// operators define how to get between the two types
public static implicit operator Amount(AmountSurrogate value)
=> Amount.CreateFrom(value.Value);
public static implicit operator AmountSurrogate(Amount value)
=> new AmountSurrogate { Value = value.ScaledValue };
}