Understanding Variance in C# Generics
Introduction
Variance in C# refers to how subtyping between more complex types relates to subtyping between their component types. Specifically, it is the ability to use a more derived type than originally specified (covariance) or a less derived type (contravariance) in generic interfaces and delegates.
Covariance
Covariance allows you to use a derived class where a base class is expected. This is useful when dealing with collections or methods that return objects. In C#, covariance is supported in generic interfaces and delegates with out type parameters.
Consider the following example:
class Base { }
class Derived : Base { }
ICovariant
ICovariant
In this case, ICovariant<Derived>
can be assigned to ICovariant<Base>
because of covariance.
Contravariance
Contravariance allows you to use a base class where a derived class is expected. This is mainly used in generic interfaces and delegates with in type parameters.
Consider the following example:
class Base { }
class Derived : Base { }
IContravariant
IContravariant
In this case, IContravariant<Base>
can be assigned to IContravariant<Derived>
because of contravariance.
Invariance
Invariance means that you cannot use a derived class or a base class where another type is expected. Simply put, you must use the exact type specified.
Consider the following example:
class Base { }
class Derived : Base { }
IInvariant
// The following line will cause a compile-time error:
// IInvariant
In this case, IInvariant<Base>
cannot be assigned to IInvariant<Derived>
because of invariance.
Practical Examples
Let's look at some practical examples where variance can be useful.
Covariance in Action
Consider a method that returns a collection of objects:
class Animal { }
class Dog : Animal { }
IEnumerable
IEnumerable
In this example, you can assign IEnumerable<Dog>
to IEnumerable<Animal>
because IEnumerable<T>
is covariant.
Contravariance in Action
Consider a method that takes a parameter of a generic type:
class Animal { }
class Dog : Animal { }
IComparer
IComparer
In this example, you can assign IComparer<Animal>
to IComparer<Dog>
because IComparer<T>
is contravariant.
Summary
Variance in C# generics is a powerful feature that allows for more flexible and reusable code. By understanding covariance, contravariance, and invariance, you can create generic interfaces and delegates that work seamlessly with a range of types, enhancing the robustness and maintainability of your applications.