如何使用protobuf-net反序列化需要ctor调用的自定义只读结构?

问题描述 投票:1回答:1

我创建了一个自定义(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对象)。

关于如何使这项工作的任何想法?

c# struct protobuf-net
1个回答
2
投票

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 };
}
© www.soinside.com 2019 - 2024. All rights reserved.