Object-oriented programming allows types to be substituted in some scenarios. For example, if a method takes an Account it is fine to pass in an instance of the subclass SavingsAccount. Contravariance turns this around for things that consume a type: something that can handle any Message can also be used where only a MyMessage handler is needed.
C# 2.0 already allowed this for delegates created from method groups (a method taking object can be used for a delegate taking string). Generic contravariance brings this to generic interfaces and delegates!
Let us consider IList<T>. We can never assign an IList<Message> to an IList<MyMessage> because the interface both accepts arguments of T (Add) and returns T (the indexer). If it were allowed, reading from it could return a Message that is not a MyMessage.
But what about an interface that only accepts T and does something with it like a message sender? Using generic contravariance solves this problem.
Code
ISender<Message> generalSender = getSender();
ISender<MyMessage> sender = generalSender; // Allowed because T is 'in'
sender.Send(new MyMessage());
...
interface ISender<in T> { void Send(T message); }ISender<Message> generalSender = getSender();
// ISender<MyMessage> sender = generalSender; // error CS0266
// Must keep using the wider ISender<Message> or write an adapter class
ISender<Message> sender = generalSender;
sender.Send(new MyMessage());
...
interface ISender<T> { void Send(T message); }.NET 4 also changed Action<T>, IComparer<T> and IEqualityComparer<T> to be contravariant, so an IComparer<Account> can sort a List<SavingsAccount>.
Notes
- It's easier to start by understanding Generic covariance C# 4.0 first
- Variance only applies to generic interfaces and delegates, and only for reference type arguments