简体   繁体   中英

Func variance with multiple parameters

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?

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.

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

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;}    }

A Person is not an Employee

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM