class Person {
static int runningId = 0;
public int Id { get; } = runningId++;
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Employee : Person {
public double Wage { get; set; }
}
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" });
}
}
static void
TakePerson(Person p) { Console.WriteLine(p.Name); }
TakeEmployeeDelegate tpd = TakePerson;
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" });
}
}
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();
}
}
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.