Search code examples
c#generics.net-4.0castingcontravariance

I can only cast a contravariant delegate with "as"


I'm trying to cast a contravariant delegate but for some reason I can only do it using the "as" operator.

interface MyInterface { }
delegate void MyFuncType<in InType>(InType input);

class MyClass<T> where T : MyInterface
{
    public void callDelegate(MyFuncType<MyInterface> func)
    {
        MyFuncType<T> castFunc1 = (MyFuncType <T>) func; //Error
        MyFuncType<T> castFunc2 = func as MyFuncType<T>; 
        MyFuncType<T> castFunc3 = func is MyFuncType<T> ? (MyFuncType<T>)func : (MyFuncType<T>)null; //Error
    }
}

castFunc2 works fine but castFunc1 and castFunc3 cause the error:

Cannot convert type 'delegateCovariance.MyFuncType<myNamespace.MyInterface>' to myNamespace.MyFuncType<T>'

The MSDN article on the as operator states that castFunc2 and castFunc3 are "equivalent" so I don't understand how only one of them could cause an error. Another piece of this that is confusing me is that changing MyInterface from an interface to a class gets rid of the error.

Can anyone help me understand what is going on here? Thanks!


Solution

  • Add a constraint such that T must be a class.

    class MyClass<T> where T: class, MyInterface
    

    This gives the compiler enough information to know that T is convertible. You don't need the explicit cast either.

    Variance only applies to reference types. T is allowed to be a value type without the constraint which breaks the compilers ability to prove that T is compatible for contravariance.

    The reason the second statement works is because as actually can perform a null conversion. For example:

    class SomeClass { }
    interface SomeInterface { }
    static void Main(string[] args)
    {
       SomeClass foo = null;
       SomeInterface bar = foo as SomeInterface;
    }
    

    Foo is obviously not directly convertable to SomeInterface, but it still succeeds because a null conversion can still take place. Your MSDN reference may be correct for most scenarios, but the generated IL code is very different which means they are fundamentally different from a technical perspective.