问题
I have the following piece of code where I am trying to write a generic validation rule for my domain objects. while doing so I have an issue to deal Func delegate supporting variance
public class Person { }
public class Employee : Person { }
internal interface IValidation<T> where T: Person
{
void AddValidationRule(Func<T,bool> predicateFunction);
}
internal class BaseValidation : IValidation<Person>
{
void IValidation<Person>.RegisterValidationRules(Person person)
{
}
}
internal class EmployeeValidation : BaseValidation
{
void RegisterValidation()
{
Func<Employee,bool> empPredicate = CheckJoiningDate;
base.AddValidationRule(empPredicate);
}
bool CheckJoiningDate(Employee employee)
{
return employee.JoiningDate > DateTime.Now.AddDays(-1) ;
}
}
With the above code in place, compiler gives an error message saying
Compiler Error on line : base.AddValidationRule(empPredicate); Argument 1: cannot convert from 'System.Func<>Employee, bool>' to 'System.Func<>Person, bool>
I had referred to this https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/dd465122%28v%3dvs.100%29 but I still couldn't make the compiler to understand about the contravariance here,
Appreciate your help so I understand this better
回答1:
cannot convert from 'System.Func<>Employee, bool>' to 'System.Func<>Person, bool>
base.AddValidationRule
requires a function that can operate on any Person
. Your function can only operate on Exployee
which is more restrictive. This is the wrong form of variance.
It is not shown here but likely BaseValidation
implemented IValidation<Person>
.
Likely, the best fix is to ensure that you inherit from IValidation<Employee>
, possibly by making BaseValidation
generic.
Would this work?
internal class BaseValidation<T> : IValidation<T>
{
void IValidation<T>.RegisterValidationRules(T entity)
{
}
}
internal class EmployeeValidation : BaseValidation<Employee>
{
//...
}
回答2:
You've mixed up covariance and contravariance.
With covariance, the generic type argument can be "smaller" than what is required. That is, if we have:
Func<Mammal, Mammal> f1 = whatever;
Func<Mammal, Animal> f2 = f1;
Why does that work? Func
is covariant in its second parameter. f2
is expecting a delegate that returns Animal
. It got a delegate that returns a smaller type; there are fewer Mammal
s than Animal
s, so Mammal
is smaller.
Think about why this works. When someone calls f2, they are expecting to get an animal back. But if they are really calling f1, they still get an animal, because every mammal is an animal.
With covariance, the "size" of the generic type varies in the same direction as the size of the type argument. Mammal
is smaller than Animal
. Func<Mammal, Mammal>
is smaller than Func<Mammal, Animal>
. That's why it is "co" variance, co meaning "together".
Contravariance is the opposite, hence "contra", meaning "against". With contravariance, the generic type argument can be bigger than expected:
Func<Giraffe, Mammal> f3 = f1;
f3 expects a function that takes a giraffe; we have a function that takes a bigger type, mammal. It's bigger because there are more mammals than giraffes. Contravariance says this is good, and that should make sense. If someone calls f3 with a giraffe, it's OK if that is actually a call to f2, because f2 can take a giraffe; it can take any mammal.
You've mixed up covariance and contravariance; you're expecting that a contravariant parameter can be used in a covariant manner, which is wrong. A function that takes employees cannot be converted to a function that takes persons, because you could pass a non-employee person to it. A function that takes employees can be converted to a function that takes managers, because all managers are employees.
来源:https://stackoverflow.com/questions/54276476/how-does-contravariance-work-with-func-delegate-in-net-core