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.
