Как десериализовать пользовательскую структуру только для чтения, требующую вызова ctor, используя protobuf-net? - PullRequest
1 голос
/ 10 марта 2019

Я создал собственную (только для чтения) структуру для инкапсуляции десятичной дроби. Я использую всюду структуру, в том числе общедоступный 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);
}

Проблема, с которой я столкнулся, заключается в том, что ctor не вызывается во время десериализации, несмотря на то, что SkipConstructor = false в ProtoContract, что приводит к правильной инициализации только свойства 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).

Есть идеи, как заставить это работать?

1 Ответ

2 голосов
/ 10 марта 2019
В частности,

struct и readonly struct - это то, к чему я планирую обратиться в v3, в котором есть планы для новых API-интерфейсов сериализатора. Между тем, это не тот сценарий, с которым он хорошо справляется, но ваша лучшая ставка может быть «суррогатами» - это означает, что сериализатор в значительной степени игнорирует Amount, используя вместо него что-то еще более удобное для сериализации. Это также означает, что вы можете удалить любые атрибуты сериализатора или API из Amount:

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