Func variance with multiple parameters
Asked Answered
C

4

5

Tried to something like this in our code but it fails:

Func<Employee, Employee> _myFunc;

void Main()
{
    Func<Employee, Employee> test1  = _myFunc;//Ok
    Func<Employee, Person> test2  = _myFunc;//Ok
    Func<Person, Employee> test3 = _myFunc;//Fails
    Func<Person, Person> test4  = _myFunc;//Fails
}

public class Person { }
public class Employee : Person { }

The last two cases give this error:

Cannot implicitly convert type System.Func<Employee, Employee> to System.Func<Person, Employee>. An explicit conversion exists (are you missing a cast?)

Any idea why?

Concede answered 15/3, 2016 at 9:18 Comment(2)
Because the return type (the last generic parameter of Func<>) is covariant, while the input parameters (all the other generic parameters of Func<>) are contravariant.Fuegian
Fun fact: conversions of method groups to delegates are also covariant and contravariant in the same way. If you have Giraffe M(Animal a) and delegate Animal D(Tiger t) then D d = M; is legal in C#, even though D is not even generic.Glim
P
12

If you look at the signature for Func<T, TResult>, you'll see that the input parameters (T in this case) are contravariant, and the return type (TResult) is covariant

public delegate TResult Func<in T, out TResult>(T arg);

Contravariance is basically about being able to pass a "bigger" type to a method expecting a "smaller" type, where covariance is exactly the opposite.

Eric Lippert puts this beautifully and elegantly (emphasis mine):

A generic type I is covariant (in T) if construction with reference type arguments preserves the direction of assignment compatibility. It is contravariant (in T) if it reverses the direction of assignment compatibility. And it is invariant if it does neither. And by that, we simply are saying in a concise way that the projection which takes a T and produces I is a covariant/contravariant/invariant projection.

Perspicacious answered 15/3, 2016 at 9:26 Comment(4)
There's a typo in Lippert's original post. It should be out T for covariant.Joab
@Joab I don't think that's what Eric meant. I'm assuming he meant that the generic type I<T> is covariant/contravariant in T, meaning for every T.Perspicacious
Yes. I read the full article and now I can realize what he meant. ThanksJoab
That is what I meant but I agree it is very confusing the way I wrote it! Regrettably I no longer have write access to msdn blogs.Glim
J
2

Because Func<T, TResult> is a defined as

public delegate TResult Func<in T, out TResult>(T arg);

As you can see, the second parameter (TResult) is indeed a covariant, but the first parameter (T, which is the input of the function) is actually a contravariant (you can only feed it with something that is less-derived).

Func<Employee, Person> is fine because it sill matches the signature, while Func<Person, Person> fails because it isn't.

See MSDN

Joab answered 15/3, 2016 at 9:26 Comment(0)
C
0

Ok, I think I understand it now:

void Main()
{
    Func<Employee, Employee> getEmployeesBoss = (Employee employee) => {return employee.Boss;};
    //This works as it expects a Person to be returned and employee.Boss is a person.
    Func<Employee, Person> getEmployeesBoss1 = getEmployeesBoss;
    //This fails as I could pass a non Employee person to this func which would not work.
    Func<Person, Employee> getEmployeesBoss2 = getEmployeesBoss;
}

class Person {} 
class Employee : Person { public Employee Boss{get;set;}    }
Concede answered 15/3, 2016 at 9:46 Comment(0)
W
-3

A Person is not an Employee

There is no cast possible between Func<Employee, xxx> and Func<Person, xxx>

Walleye answered 15/3, 2016 at 9:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.