This question has already been answered:

Why are the parameters T1 , T2 , ... are contravariant ( in T1 , in T2 , ...) in Action ?
And why is the return type in Func is out TResult ?

Reported as a duplicate by participants Andrey NOP , PashaPash c # Nov 23 '17 at 15:43

A similar question was asked earlier and an answer has already been received. If the answers provided are not exhaustive, please ask a new question .

  • @VladD, why not close as a duplicate? - Andrei NOP
  • @Andrey : Because I’m not 100% sure - VladD
  • @VladD kind of duplicate ... A priori knew what covariance / countervariance is and how. The question was about the architectural decision: why delegates are so. Based on the answers, everything fell into place. Thank! - khirnick

2 answers 2

On the fingers: if you need a hairdresser for dogs ( Action<Dog> ), a universal hairdresser for animals (a function that accepts Animal input) is suitable.

Therefore, an Action<Dog> variable can be assigned an Action<Animal> expression.


On the contrary, if you need to get any kind of animal ( Func<Animal> ), then the kitty shop is suitable ( Func<Kitten> ).

Therefore, a variable of type Func<Animal> can be assigned an expression of type Func<Kitten> .


In more detail: What is the essence of the covariance and contravariance of delegates? Contravariance of generalized C # delegates .

  • I do not agree, if I need a hairdresser for dogs, then a general hairdresser for animals will not suit me at all. A hairdresser for all animals will not be able (most likely) to mow a dog normally. - ixSci
  • @ixSci: Once he declares himself a hairdresser for all animals, let him cut the dog. If he does not know how to cut any animal, but only hedgehogs, raccoons and echidnians, then he is not a universal hairdresser. - VladD

Action delegates are countervariant because where you can use an instance of an ancestor class, you can also use an instance of a descendant in the same place. Example:

 Action<string> act1 = str => { int i = str.Length; }; Action<object> act2 = o => { string s = o.ToString(); }; // вы можете сделать такое присваивание act1 = act2; // и затем вызвать делегат вот так, // потому что по факту у вас будет вызван 2-ой делегат, // которому будет передана string, производная от object act1("10"); 

Since string is a descendant of object , you can do the same with this parameter as with object . There is no error. However, you can not do this:

 act2 = act1; 

Because in this case, you could pass some object to a method that actually requires a string , and this is already an error.

Covariance of Func is similarly explained:

 Func<string> func1 = () => "str"; Func<object> func2 = () => 10; // такое присваивание тоже возможно func2 = func1; // ваш делегат вернёт некий object, который по факту является строкой. object r = func2(); 

Similar to the previous example, you can not do this:

 func1 = func2; 

since in this case the delegate that returns a string , you can assign a delegate that returns an object (which in our example is actually of type int ).