Object-oriented programming allows types to be substituted in some scenarios. For example, if a method returns us an Account it is fine for that method to return us a SavingsAccount instead. Covariance extends this idea to types built from other types: if a SavingsAccount is an Account, then a sequence of SavingsAccount can be used as a sequence of Account.
C# 1.0 already had covariance for arrays (Account[] all = new SavingsAccount[10];, which can fail at runtime when writing), and C# 2.0 allowed a method returning a more derived type to be used for a delegate. Generic covariance brings covariance to generic interfaces and delegates.
Let us consider IList<T>. We can never assign an IList<SavingsAccount> to an IList<Account> because the interface both accepts arguments of T (Add) and returns T (the indexer). If it were allowed, someone could Add a plain Account to a list that must only hold savings accounts.
But what about an interface that only returns instances of T like an enumerable? With generic covariance we can do this!
Code
IEnumerable<Account> accounts = getAccounts<SavingsAccount>();
foreach(var account in accounts)
PrintStatement(account);// IEnumerable<Account> accounts = getAccounts<SavingsAccount>(); // error CS0266
List<Account> accounts = new List<Account>();
foreach(SavingsAccount savings in getAccounts<SavingsAccount>())
accounts.Add(savings);
foreach(Account account in accounts)
PrintStatement(account);This was achieved in .NET 4 by Microsoft changing IEnumerable<T> to IEnumerable<out T>.
Notes
- Variance only applies to generic interfaces and delegates, not classes, so
List<SavingsAccount>still cannot be assigned toList<Account> - Variance only works for reference types:
IEnumerable<int>cannot be used asIEnumerable<object> - The reverse of this - accepting a more specific argument rather than having a more specific one returned to you - is Generic contravariance C# 4.0
- Covariant returns C# 9.0 later allowed overriding methods to return a more derived type