I am having trouble understanding the difference between covariance and contravariance.
It's probably easiest to give examples - that's certainly how I remember them.
Covariance
Canonical examples: IEnumerable
, Func
You can convert from IEnumerable
to IEnumerable
, or Func
to Func
. Values only come out from these objects.
It works because if you're only taking values out of the API, and it's going to return something specific (like string
), you can treat that returned value as a more general type (like object
).
Contravariance
Canonical examples: IComparer
, Action
You can convert from IComparer
to IComparer
, or Action
to Action
; values only go into these objects.
This time it works because if the API is expecting something general (like object
) you can give it something more specific (like string
).
More generally
If you have an interface IFoo
it can be covariant in T
(i.e. declare it as IFoo
if T
is only used in an output position (e.g. a return type) within the interface. It can be contravariant in T
(i.e. IFoo
) if T
is only used in an input position (e.g. a parameter type).
It gets potentially confusing because "output position" isn't quite as simple as it sounds - a parameter of type Action
is still only using T
in an output position - the contravariance of Action
turns it round, if you see what I mean. It's an "output" in that the values can pass from the implementation of the method towards the caller's code, just like a return value can. Usually this sort of thing doesn't come up, fortunately :)