Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

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:

interface ICovariant { }
class Base { }
class Derived : Base { }
ICovariant derived = null;
ICovariant baseType = derived;

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:

interface IContravariant { }
class Base { }
class Derived : Base { }
IContravariant baseType = null;
IContravariant derived = baseType;

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:

interface IInvariant { }
class Base { }
class Derived : Base { }
IInvariant baseType = null;
// The following line will cause a compile-time error:
// IInvariant derived = baseType;

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:

interface IEnumerable { }
class Animal { }
class Dog : Animal { }
IEnumerable dogs = null;
IEnumerable animals = dogs;

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:

interface IComparer { }
class Animal { }
class Dog : Animal { }
IComparer animalComparer = null;
IComparer dogComparer = animalComparer;

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.