What is Covariance in C#?
Have you ever wondered why the C# compiler does not complain when you feed lists of strings into a function that expects a list of objects? Why does that work out of the box? After reading this, you understand how Covariance helps us to convert IEnumerable of string to IEnumerable of object.
Suppose we have a list of strings
var s = new List<string>();
and a function that takes a list of objects
void Test(IEnumerable<object> input)
Without Covariance the compiler would give us a build error like:
Cannot convert List<string> to IEnumerable<object>
But C# (4.0 and up) has Covariance that allows passing lists of subtypes.
Because strings derive from (and therefore are) objects, covariance in C# allows conversion of a list of
strings
to a list ofobjects
.
Now we know why it works. But how does it work?
If you go to the definition of IEnumerable<T>
, you see the out
keyword:
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
The trick is in the out
keyword. This is called a generic modifier and should not be confused with out-parameters. Perhaps you asked yourself the question: “How does the generic List
implementation perform the cast to another type?”. It does not. The out
keyword makes the compiler do this for us.
Here is a console app example so you can test it yourself:
// covariance is used to convert IEnumerable<string> to IEnumerable<object>. This works assuming that:
// 1) The generic IEnumerable interface is marked with the 'out' keyword (IEnumerable<out T>). Which is true in C#.
// 2) string derives from (and therefore is) an object.
class Program
{
static void Main(string[] args)
{
var s = new List<string>();
// it is allowed to pass a list of strings when a list of objects is expected because the compiler
// uses covariance to convert ienumerable<string> to ienumerable<object>
Test(s);
}
static void Test(IEnumerable<object> input)
{
foreach (var item in input)
{
}
}
}
Here’s a list of Covariant interfaces in C#.