C# Generic Variance
Variance is about when a generic type of one type may stand in for a generic type of another. The answer depends on whether the shape only reads or also writes.
Reading dogs as animals
IEnumerable only reads its members. A list of dogs can be read through it as animals.
using System;
using System.Collections.Generic;
class Animal
{
public string Name = "";
}
class Dog : Animal
{
}
class Program
{
static void Main(string[] args)
{
List<Dog> dogs = new List<Dog>();
Dog rex = new Dog();
rex.Name = "Rex";
dogs.Add(rex);
IEnumerable<Animal> animals = dogs;
foreach (Animal a in animals)
{
Console.WriteLine(a.Name);
}
}
}Two dogs through the wide shape
The widened view reads every member in list order. The list itself keeps its Dog type.
using System;
using System.Collections.Generic;
class Animal
{
public string Name = "";
}
class Dog : Animal
{
}
class Program
{
static void Main(string[] args)
{
List<Dog> dogs = new List<Dog>();
Dog rex = new Dog();
rex.Name = "Rex";
dogs.Add(rex);
Dog fido = new Dog();
fido.Name = "Fido";
dogs.Add(fido);
IEnumerable<Animal> animals = dogs;
foreach (Animal a in animals)
{
Console.WriteLine(a.Name);
}
}
}A list may not widen
List can both read and add. The compiler refuses to treat a list of dogs as a list of animals.
using System;
using System.Collections.Generic;
class Animal
{
public string Name = "";
}
class Dog : Animal
{
}
class Program
{
static void Main(string[] args)
{
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs;
Console.WriteLine(animals.Count);
}
}Example
using System;
using System.Collections.Generic;
class Animal
{
public string Name = "";
}
class Dog : Animal
{
}
class Program
{
static void PrintNames(IEnumerable<Animal> animals)
{
foreach (Animal a in animals)
{
Console.WriteLine(a.Name);
}
}
static void Main(string[] args)
{
List<Dog> dogs = new List<Dog>();
Dog rex = new Dog();
rex.Name = "Rex";
dogs.Add(rex);
List<Animal> pets = new List<Animal>();
Animal plain = new Animal();
plain.Name = "Plain";
pets.Add(plain);
PrintNames(dogs);
PrintNames(pets);
}
}Key points
- A Dog is an Animal wherever inheritance applies.
- A read-only shape may widen to the base type.
- IEnumerable of Animal can read a list of Dog values.
- A writable shape may not widen.
- A List of Animal could gain a Cat, so a List of Dog cannot stand in for it.
- The compiler enforces the rule at compile time.
In this example, PrintNames takes a read-only sequence of animals. The list of dogs passes through the wide shape and prints Rex. The list of animals prints Plain. One reading method serves both lists.
Note
Read-only shapes widen. Writable shapes stay exact.
Try it Yourself
Edit the code, then press Run.
Running is simulated
Exercise
Why can a List<Dog> be read as IEnumerable<Animal> but not assigned to List<Animal>?