Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicit cast to defined type throws `InvalidCastException`

Tags:

c#

casting

I have some custom wrapper types defined with an explicit cast operation:

private class A
{
    private readonly int _value;

    public A(int value)
    {
        _value = value;
    }

    public int Value { get { return _value; } }
}

private class B
{
    private readonly int _value;

    private B(int value)
    {
        _value = value;
    }

    public int Value { get { return _value; } }

    public static explicit operator B(A value)
    {
        return new B(value.Value);
    }
}

The following works just fine:

B n = (B)new A(5);

This does not:

B n = (B)(object)new A(5);

// Throws System.InvalidCastException:
// Unable to cast object of type 'A' to type 'B'

...what's going on here?
I'm constrained to have the cast to object first - the actual cast is being performed elsewhere in generic library code (so it's of the form return (TOutput)(object)input;, and dynamic is not an option)

like image 531
Clockwork-Muse Avatar asked Feb 15 '15 02:02

Clockwork-Muse


1 Answers

The reason the first code snippet works is that what you see is not a "straight" cast: it is an invocation of an explicit conversion operator. This operation is well-defined, and works exactly the way it is coded: a brand-new object B is constructed from A's value. C# has enough information to determine that the explicit operator is to be invoked, because new A(5) type matches the type of operator B(A value) parameter.

Once you add a cast to object, however, the compiler does a true cast. C# no longer has an applicable user-defined conversion operator, so it does the type check, determines that A is not convertible to B, and throws an exception.

There is a way to make it work without dynamic by building and compiling a dynamic lambda, like this:

private static B ConvertFromObject(object a) {
    if (a == null) return null;
    var p = Expression.Parameter(typeof(object));
    var c1 = Expression.Convert(p, a.GetType());
    var c2 = Expression.Convert(c1, typeof(B));
    var e = (Func<object,B>)Expression.Lambda(c2, p).Compile();
    return e(a);
}

Demo.

You could cache compiled lambdas by a's runtime type to save on the costs of compiling a new expression each time you run the method.

like image 152
Sergey Kalinichenko Avatar answered Nov 14 '22 21:11

Sergey Kalinichenko