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