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!
NET Framework 4 and later versions, C# supports covariance and contravariance in generic interfaces and delegates and allows for implicit conversion of generic type parameters. For more information, see Variance in Generic Interfaces (C#) and Variance in Delegates (C#).
A type can be declared contravariant in a generic interface or delegate only if it defines the type of a method's parameters and not of a method's return type. In , ref , and out parameters must be invariant, meaning they are neither covariant nor contravariant.
Covariance permits a method to have a return type that is a subtype of the one defined in the delegate. Contravariance permits a method to have a parameter type that is a base type of the one defined in the delegate type.
Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types.
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.
Eric Lippert gave a great explanation of this issue in his recent posts: An "is" operator puzzle, part one, An "is" operator puzzle, part two.
Main rationale behind this behavior is following: "is" (or "as") operators are not the same as a cast. "as" operator can result non-null result event if corresponding cast would be illegal, and this is especially true when we're dealing with type arguments.
Basically, cast
operator in your case means (as Eric said) that "I know that this value is of the given type, even though the compiler does not know that, the compiler should allow it" or "I know that this value is not of the given type; generate special-purpose, type-specific code to convert a value of one type to a value of a different type."
Later case deals with value conversions like double-to-integer conversion and we can ignore this meaning in current context.
And generic type arguments are not logical in the first context neither. If you're dealing with a generic type argument, than why you're not stating this "contract" clearly by using generic type argument?
I'm not 100% sure what you're want to achieve, but you can omit special type from your method and freely use generic argument instead:
class MyClass<T> where T : MyInterface { public void callDelegate(Action<T> func) { } } class MyClass2 { public void callDelegate<T>(Action<T> func) where T : MyInterface { } }
Otherwise you should use as
operator with check for null instead of type check.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With