Covariance
//
Contravariance
Steven Giesel
Contravariance and
covariance are essential
concepts in C# when dealing
with generics, enabling us to
have more flexibility when
assigning generic types.
Covariance
Allows a method to return a more derived type
than specified by the generic type. In other
words, you can use a derived class where a base
class is expected.
To mark an interface as covariant we can use the
out keyword.
IEnumerable<out T> is covariant, meaning that if
Dog is a subclass of Animal, you can use
IEnumerable<Dog> where IEnumerable<Animal> is
expected.
Contravariance
Enables you to use a more generic (less derived)
type than specified by the generic type. In other
words, you can use a base class where a derived
class is expected.
To mark an interface as covariant we can use the
in keyword.
IComparer<in T> is contravariant, meaning that if
Animal is a base class of Dog, you can use
IComparer<Animal> where IComparer<Dog> is
expected.
What code does it make easier to use
contravariance? Why not using
generic's in this case or directly the
base type?
Contravariance
Contravariance simplifies code by allowing you to
create more reusable and maintainable
components, especially when working with
delegates and interfaces.
While generics alone provide type safety,
contravariance offers an additional layer of
flexibility.
Consider the following example using the base
type directly:
Contravariance
Now imagine we have a method that takes a list
of dogs and an AnimalHandler:
This works fine for an AnimalHandler instance,
but if you want to use a DogHandler, you'll have
to modify the ProcessDogs method. This creates
tight coupling and reduces reusability.
Now let's use contravariance with an interface:
Contravariance
With contravariance, the ProcessDogs method
can accept an IHandler<Dog> instead:
Contravariance
Why not something like this? With
generic constraints?
Contravariance
It's essential to understand the differences
between using constraints and using
contravariance to choose the best approach for
your needs.
By defining IHandler<TAnimal> with a constraint,
you can implement specialized handlers:
Contravariance
The ProcessDogs method will have to be defined
as:
The primary difference is that the ProcessDogs
method is now generic, and the type safety and
flexibility come from the generic type constraint.
In contrast, contravariance provides flexibility
directly in the interface definition without the
need for a generic method.