IComparer is a good example to demonstrate this. IComparer looks like this:
IComparer<in T>
Take the following:
IComparer<Primate>
IComparer<Chimpanzee>
where Chimpanzee : Primate
(of course). A method which requires an IComparer<Chimpanzee>
can take an IComparer<Primate>
as an argument, because if the comparer can compare primates, it can also compare chimpanzees, as it uses traits common to the two types to affect the comparison.
A good way to think of this is in terms of functionality. Covariance allows more complex objects to be passed which implement a core functionality (like passing a string for an object). Contravariance does something similar... comparing all primates is more complex than just comparing chimpanzees. It allows you to replace a comparer for a specific type with one which compares a more general type. In this sense, the "in" applies more to the functionality of the method than the actual type passed.