When is an IEnumerable<T>
a subtype of a different IEnumerable<T1>
? When T
is a subtype of T1
. IEnumerable
is covariant in its T
parameter, which means that IEnumerable
's subtype relationship goes in the same direction as T
's.
class Animal { /* ... */ }
class Dog : Animal { /* ... */ }
IEnumerable<Dog> dogs = Enumerable.Empty<Dog>();
IEnumerable<Animal> animals = dogs; // IEnumerable<Dog> is a subtype of IEnumerable<Animal>
// dogs = animals; // Compilation error - IEnumerable<Animal> is not a subtype of IEnumerable<Dog>
An instance of a covariant generic type with a given type parameter is implicitly convertible to the same generic type with a less derived type parameter.
This relationship holds because IEnumerable
produces T
s but doesn't consume them. An object that produces Dog
s can be used as if it produces Animal
s.
Covariant type parameters are declared using the out
keyword, because the parameter must be used only as an output.
interface IEnumerable<out T> { /* ... */ }
A type parameter declared as covariant may not appear as an input.
interface Bad<out T>
{
void SetT(T t); // type error
}
Here's a complete example:
using NUnit.Framework;
namespace ToyStore
{
enum Taste { Bitter, Sweet };
interface IWidget
{
int Weight { get; }
}
interface IFactory<out TWidget>
where TWidget : IWidget
{
TWidget Create();
}
class Toy : IWidget
{
public int Weight { get; set; }
public Taste Taste { get; set; }
}
class ToyFactory : IFactory<Toy>
{
public const int StandardWeight = 100;
public const Taste StandardTaste = Taste.Sweet;
public Toy Create() { return new Toy { Weight = StandardWeight, Taste = StandardTaste }; }
}
[TestFixture]
public class GivenAToyFactory
{
[Test]
public static void WhenUsingToyFactoryToMakeWidgets()
{
var toyFactory = new ToyFactory();
//// Without out keyword, note the verbose explicit cast:
// IFactory<IWidget> rustBeltFactory = (IFactory<IWidget>)toyFactory;
// covariance: concrete being assigned to abstract (shiny and new)
IFactory<IWidget> widgetFactory = toyFactory;
IWidget anotherToy = widgetFactory.Create();
Assert.That(anotherToy.Weight, Is.EqualTo(ToyFactory.StandardWeight)); // abstract contract
Assert.That(((Toy)anotherToy).Taste, Is.EqualTo(ToyFactory.StandardTaste)); // concrete contract
}
}
}