2

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://learn.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

user824910
  • 1,067
  • 6
  • 16
  • 38

2 Answers2

8

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 Mammals than Animals, 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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I suspect that variance will not fix his problem. `AddValidationRule` sounds like a collection of rules is built and later executed. It seems that rule set must use the exact type of the objects that will be passed in for validation. We will see when the OP responds. – usr Jan 20 '19 at 15:39
  • @Eric Thank you for your quick brief. I'm still wondering why we cannot assign a function that takes employee to a function that takes person? assuming there are fewer employees than persons. similar to fewer Mamals than Animal. I am trying to get my understanding right as I am learning. – user824910 Jan 20 '19 at 18:48
  • @usr you are right, I am trying to build a collection of rules to be executed later. – user824910 Jan 20 '19 at 18:49
  • 1
    @user824910: Suppose that were legal. `Action a1 = d => d.Bark(); Action a2 = a1; a2(new Giraffe());` and we just made a giraffe bark. Obviously that program has to be wrong; which line is wrong? The first line is clearly legal; barking is an action of dogs. The last line is clearly legal; you can pass a giraffe to an action that takes animals. Therefore it must be the second line that is illegal. – Eric Lippert Jan 20 '19 at 19:00
  • 1
    @user824910: Now do it the other way: `Action a1 = d => d.Bark(); Action a3 = a1; a3(new Poodle());` We just made a poodle bark; nothing wrong there. This program is legal. – Eric Lippert Jan 20 '19 at 19:01
  • 2
    @user824910: The *inputs* of a function can be made *smaller*; a function that takes people can take any employee. But they cannot be made *larger*; a function that takes only employees cannot take an arbitrary person because the function might depend on a property of employees that is not found in a non-employee. – Eric Lippert Jan 20 '19 at 19:02
  • @EricLippert so are we saying that the JoiningDate in Employee will be dependent on employee and hence it is not valid to make it as Person? If so, please advise the right way to achieve a generic validator functionality for all entity modelled – user824910 Jan 20 '19 at 19:17
  • Offhand I do not know how to do that, sorry. – Eric Lippert Jan 20 '19 at 19:28
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>
{
 //...
}
usr
  • 168,620
  • 35
  • 240
  • 369
  • I have added Basevalidation. As you mentioned, I was trying to create a collection of rules which can be executed later. I understand the variance difference as explained by Eric, any idea how I could do this better? – user824910 Jan 21 '19 at 05:40
  • @user824910 I have added a proposal. – usr Jan 21 '19 at 10:40
  • ah! I missed the generic implementation part.. Thanks for helping me on this. – user824910 Jan 21 '19 at 19:04