I am learning "contravariant generic delegate".
My understanding is:
The "in" keyword specifies that the type parameter is contravariant.
This allows for implicit conversion of delegate types.If there is no "in" keyword, we don't know if the type parameter is contravariant.
Then implicit conversion of delegate types are not allowed.
Here is my code:
public class Test
{
//public delegate bool FuncDelegate<T>(T t);
public delegate bool FuncDelegate<in T>(T t);
public class BaseClass
{
public int x;
}
public class DerivedClass: BaseClass
{
public int y;
}
static bool BaseFunc(BaseClass bc)
{
if (bc.x > 1)
return false;
else
return true;
}
static bool DerivedFunc(DerivedClass dc)
{
if (dc.y > 1)
return false;
else
return true;
}
public static void Main()
{
FuncDelegate<DerivedClass> genericDerivedFunc = DerivedFunc;
FuncDelegate<BaseClass> genericBaseFunc = BaseFunc;
genericDerivedFunc = genericBaseFunc;
FuncDelegate<DerivedClass> genericDerivedFunc2 = BaseFunc;
}
}
My question
/*
This line is valid when declared as: public delegate bool FuncDelegate<in T>(T t);
This line is invalid when declared as: public delegate bool FuncDelegate<T>(T t);
*/
genericDerivedFunc = genericBaseFunc;
This line agrees with my undertanding.
/*
This line is always valid.
*/
FuncDelegate<DerivedClass> genericDerivedFunc2 = BaseFunc;
I don't understand this line:
"bool BaseFunc(BaseClass bc)" can implicitly converts to bool "FuncDelegate<DerivedClass>(DerivedClass t)".
I think it must have the "in" keyword to specifies contravariant.
But the conversion can be done without the "in" keyword.
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.
In . 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.
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.
Delegates defined within a generic class can use the generic class type parameters in the same way that class methods do. Generic delegates are especially useful in defining events based on the typical design pattern because the sender argument can be strongly typed and no longer has to be cast to and from Object.
An interface or delegate type can have both covariant and contravariant type parameters. Visual Basic and C# do not allow you to violate the rules for using covariant and contravariant type parameters, or to add covariance and contravariance annotations to the type parameters of types other than interfaces and delegates.
Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types. When you are referring to a type system, covariance, contravariance, and invariance have the following definitions.
However, variance in delegate binding works with all delegate types, not just with generic delegate types that have variant type parameters.
The last generic type parameter of the Func generic delegates specifies the type of the return value in the delegate signature. It is covariant (out keyword), whereas the other generic type parameters are contravariant (in keyword).
Note the difference between the right hand sides of these two assignments:
genericDerivedFunc = genericBaseFunc;
genericDerivedFunc2 = BaseFunc;
The first line's right hand side is a delegate, so you are converting a delegate type to another delegate type. This requires a variance conversion, as listed in the available conversions in the C# spec:
The implicit reference conversions are:
- ...
- From any reference_type to an interface or delegate type T if it has an implicit identity or reference conversion to an interface or delegate type T0 and T0 is variance-convertible to T.
And variance conversions require those in
s and out
s.
On the second line though, the right hand side is a method group (the name of a method), so on the second line, you are actually doing a method group conversion. For such a conversion to be available, BaseFunc
needs to be compatible with the target delegate type. Note that this is a requirement about the method, not a requirement on the delegate type. To be "compatible".
Notably, two of the requirements for method M
to be "compatible" with a delegate type D
are:
- For each value parameter, an identity conversion or implicit reference conversion exists from the parameter type in
D
to the corresponding parameter type inM
.- An identity or implicit reference conversion exists from the return type of
M
to the return type ofD
.
These are the requirements that make it look as if the delegate type had the in
modifier on all its parameters, and out
on its return type.
Basically, because the RHSs are very different kind of things, different rules apply.
Docs cover this in variance in delegates section:
.NET Framework 3.5 introduced variance support for matching method signatures with delegate types in all delegates in C#. This means that you can assign to delegates not only methods that have matching signatures, but also methods that return more derived types (covariance) or that accept parameters that have less derived types (contravariance) than that specified by the delegate type. This includes both generic and non-generic delegates.
So in your case this directly falls under the "accept parameters that have less derived types (contravariance) than that specified by the delegate type" part.
If you see the decompilation by sharplab you see that FuncDelegate<DerivedClass> genericDerivedFunc2 = BaseFunc;
is actually converted to something like:
FuncDelegate<DerivedClass> genericDerivedFunc2 = new FuncDelegate<DerivedClass>(BaseFunc);
And genericDerivedFunc = genericBaseFunc;
is just a plain assignment of FuncDelegate<BaseClass>
to FuncDelegate<DerivedClass>
which will fail when FuncDelegate
is not contravariant.
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