1
votes

I have a class:

internal class Value
{
    internal IConvertible Min { get; set; }
}

and a test case:

[TestMethod]
public void Custom_IConvertible()
{
    RuntimeTypeModel.Default.
        Add(typeof(Value), false).
        Add(1, "Min");

    RuntimeTypeModel.Default.
        Add(typeof(IConvertible), false);

    var val = new Value {Min = true};
    var result = Serializer.DeepClone(val);
    Assert.AreEqual(true, result.Min);
}

that throws:

System.InvalidOperationException: Unexpected sub-type: System.Boolean

But I cannot add boolean as a subtype of IConvertible, because I will get:

System.ArgumentException: Data of this type has inbuilt behaviour, and 
cannot be added to a model in this way: System.Boolean 

As I see it, IConvertible is only implemented by BCL base types and this scenario should be working just OK. What am I missing here?


Inspired by Marc's response below, I rewrote my sample.

Having a class:

internal abstract class Value
{
    internal IConvertible Min { get; set; }

    internal static Value Create<T>(T min) where T :IConvertible
    {
        return new ValueT<T> {Min = min};
    }

    internal sealed class ValueT<T> : Value where T : IConvertible
    {
        internal new T Min
        {
            get { return (T) base.Min; }
            set { base.Min = value; }
        }
    }
}

and a test case:

[TestMethod]
public void Custom_IConvertible()
{
    RuntimeTypeModel.Default.
        Add(typeof(Value), false).
        AddSubType(10, typeof(Value.ValueT<bool>));

    RuntimeTypeModel.Default.
        Add(typeof(Value.ValueT<bool>), false).
        Add(1, "Min");

    var val = Value.Create(true);
    var result = Serializer.DeepClone(val);
    Assert.AreEqual(true, result.Min);
}

I get an exception when calling Add(1, "Min"):

System.ArgumentException: Unable to determine member: Min
Parameter name: memberName

However, it will work just fine if I were to define a class as (same code, but with attributes):

[ProtoContract]
[ProtoInclude(1, typeof(ValueT<int>))]
internal abstract class Value
{
    internal IConvertible Min { get; set; }

    internal static Value Create<T>(T min) where T :IConvertible
    {
        return new ValueT<T> {Min = min};
    }

    [ProtoContract]
    internal sealed class ValueT<T> : Value where T : IConvertible
    {
        [ProtoMember(1)]
        internal new T Min
        {
            get { return (T) base.Min; }
            set { base.Min = value; }
        }
    }
}

[TestMethod]
public void Custom_IConvertible()
{
    var val = Value.Create(true);
    var result = Serializer.DeepClone(val);
    Assert.AreEqual(true, result.Min);
}

but I cannot use attributes.

Is it a bug or I'm missing something obvious?

1

1 Answers

2
votes

That isn't going to work. protobuf-net works against data contracts (essentially), and IConvertible is not a contract - the actual value at runtime could be anything. protobuf-net wants to know in advance what it is.

There is support for interfaces in protobuf-net, but that is for different scenarios.

The fact that they are BCL primitives is irrelevant; and for exactly the same reason, internal object Min {get;set;} would fail.

tl;dr: that is not a supported scenario.


Here's a re-working that can work (note that for convenience I've used attributes for configuration, but it would work via runtime configuration too):

using ProtoBuf;
using System;
[ProtoContract]
[ProtoInclude(1, typeof(ValueImpl<int>))]
[ProtoInclude(2, typeof(ValueImpl<float>))]
[ProtoInclude(3, typeof(ValueImpl<double>))]
// other types you want to support
internal abstract class Value
{
    public static Value Create<T>(T value) where T : IConvertible
    {
        return new ValueImpl<T> { Min = value };
    }
    public IConvertible Min { get { return MinImpl;} set { MinImpl = value;}}
    protected abstract IConvertible MinImpl {get;set;}
    [ProtoContract]
    public sealed class ValueImpl<T> : Value where T : IConvertible
    {
        [ProtoMember(1)]
        public new T Min { get; set; }
        protected override IConvertible MinImpl
        {
            get { return Min; }
            set { Min = (T)value; }
        }
    }
}

static class Program
{
    static void Main()
    {       
        var val = Value.Create(true);
        var result = Serializer.DeepClone(val);

        bool x = (bool)val.Min; // true
    }
}