Monday, 6 March 2017

Contravariance

Contravariance is in a way the opposite of Covarience, where covarience lets you treat a derived type as a base type, contravariance allows you to use a base type as a derived type. Covariance was concerned with return types, Contravariance applies to parameters, lets' take a look at the following classes.

class Person {
    static int runningId = 0;

    public int Id { get; } = runningId++;
    public string FirstName { getset; }
    public string LastName { getset; }
}

class Employee : Person {
    public double Wage { getset; }
}

if we where able to assign a Person to a variable with type student then that would be contravariance, but alas our example can't be that easy because that's not allowed. However if we look at delegates

class Program
{
    delegate void TakeEmployeeDelegate(Employee emp);

    static void TakeEmployee(Employee e) Console.WriteLine(e.Name); }

    static void Main(string[] args)
    {
        TakeEmployeeDelegate ted = TakeEmployee;
        ted.Invoke(new Employee { Name = "pawel" });
    }
}

we see that we've defined a "TakeEmployeeDelegate" that takes in an Employee type, then we assign it our TakeEmployee method to it, which makes complete sense, everything expects an employee we always supply an employee and all is well; however let's create another method, one that takes in a Person as a parameter.

static void TakePerson(Person p) Console.WriteLine(p.Name); }

next let's assign it to an instance of our "TakeEmployeeDelegate"

TakeEmployeeDelegate tpd = TakePerson;

we'll that's bizarre, there's no compile time exception, we know that an employee is a person, so our first try with TakeEmployee makes sense, but we know that a Person isn't necessarily an employee so it's kind of unexpected that we can assign our TakePerson method to our TakeEmployeeDelegate. however just because we can assign a method to a delegate doesn't mean we can pass in a person.

class Program
{
    delegate void TakeEmployeeDelegate(Employee emp);

    static void TakeEmployee(Employee e) { Console.WriteLine(e.Name); }
    static void TakePerson(Person p) { Console.WriteLine(p.Name); }

    static void Main(string[] args)
    {
        TakeEmployeeDelegate ted = TakeEmployee;
        ted.Invoke(new Employee { Name = "pawel" });

        TakeEmployeeDelegate tpd = TakePerson;
        tpd.Invoke(new Employee { Name = "Tomek" });
    }

}

we are still restricted to passing an employee to our instance of TakeEmployeeDelegate, however it's contravariance that allows us to assign a method that takes base type as a parameter to a delegate that expects a derived type.

more or less since we know that an Employee will always be a Person, when we assign a method or function that expects a Person as a parameter to a delegate that expects an "Employee" as a parameter, the compiler says, "YEA BUDDY" because we know that later when we pass an Employee in it'll definitely be a Person since it inherits from one, however since the delegate restricts the type to Employee, we can only pass types that are or inherit from employee, but we can treat them as the "Person" type, because we know that Employee is a Person.

now let's take a look at a slightly less contrived and a vaguely more realistic example

class Person
{
    static int runningId = 0;
    public int Id { get; protected set; } = runningId++;
    public string Name { get; set; }
}

class Employee : Person, ICloneable
{
    public override string ToString() { return $"Employee {Name}"; }
    public object Clone() {
        return new Employee { Id = this.Id, Name = this.Name };
    }
}
class Teacher : Employee
{
    public override string ToString() { return $"Teacher {Name}"; }
}

class PeopleComparer: IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y) { return x.Id == y.Id; }

    public int GetHashCode(Person obj) { return obj.Id; }
}

class Program
{
    static void Main(string[] args)
    {
        var t1 = new Teacher { Name = "Nicole" };
        var t2 = new Teacher { Name = "Daniela" };
        var e1 = new Employee { Name = "Ian" };
        var e2 = new Employee { Name = "Tomek" };
        var e3 = new Employee { Name = "Pawel" };
        var e4 = e1.Clone() as Employee;
        var e5 = t1.Clone() as Employee;

        var Employees = new List<Employee>(new Employee[] { t1, t2, e1, e2, e3, e4, e5 });

        var distinctEmps = Employees.Distinct<Employee>(new PeopleComparer()).ToList();
    }

}

since we know that teachers are always employees we can store both of them in a list of employees, now we can create a PeopleComparere that lets us know if two Person instances are equal, it does this by comparing hashcodes, and if objects have equal hashcodes then they're compared via the equal function.

now our PeopleComparer expects a Person as an input but the distinct function on our employees list expects to use an EmployeeComparere that implements IEqualityComparer<Employee> so we are using a base type when we are expecting a derived type.

the compiler is saying I'm expecting a comparer that compares Employees, but since employees are people, a Comparer that compares people will work aswell.

The take away: contravariece has to do with parameters, it lets you use a base type when a derived type is expected.