In my last post I had discussed about delegates in C# but intentionally skipped the covariance and contravariance with respect to delegates.I would like to discuss the concepts of covariance and contravariance in general first and then correlate the same in context of C# delegates.The concept of covariance and contravariance I found a bit confusing and counterintuitive initially.
Let us consider a type/class P and let P” be a subtype/subclass of T.We will denote this relationship as P”->P for sake of discussion.Based on this inheritance relationship following statements are true:
- Domain of P is much broader than P”.
- Any context where P is expected we can substitute it by P” [Liskov Substitution Principle]
Similarly we have another pair of classes R and R” where R”->R.
Now we will consider a function f which type P as parameter and returns type R.We will denote this as R f(P).Similarly there is another function f” as R”f”(P”) which accepts P” as parameter and returns type R”.
Can we say that f”->f or we can replace f by f” in any context?
In order to replace f by f”, f” should be able to handle parameter of type P and return value of type R.
Now R” is a subclass of R,so R” is a type of R.Hence we can say f” basically returns a type R.This is covariance by which we can replace a method by another having a more derived return type.
Now let’s see can f” handle a parameter of type P.The answer is NO.The logic built into f” is only for the possible values of P” and P can have more possible values than P” as it is the super class.So f” will malfunction for all those values of P which are outside the range of P”.So f” can replace f only if parameter of f is more derived than that of f”.This is contravariance by which we can replace a method by another having parameter types that are less derived.
If we redefine f as R f(P”) and f” as R” f”(P) then we can say f”->f and can replace f in any context.
Now let us consider an example with some real life examples.
There is a base class called Order and Order can be of two derived types say SalesOrder and PurchaseOrder.Similarly there is a base class called AccountRecord and there are two derived classes say AccountPayable and AccountReceivable.
There are two methods
- M1 – AccountRecord GetAccountInfo(Order o)
- M2 – AccountPayable GetAccountInfo(PurchaseOrder po)
M2 cannot accept a variable of type O as parameter because if it is an instance of SalesOrder instead of PurchaseOrder this will fail.
But calling code which expects AccountRecord as output can very well handle both AccountPayable and AccountReceivable.
We can relate this to a delegate in the following way:
public delegate object ReadObjectDelegate(FileStream fs)
We can attach the following method to this delegate
public string Read(Stream s)
This is because Stream is less derived than FileStream and string is more derived than object