There is already a very good answer from Eric. Just wanted to take this chance to talk about the Invariance, Covariance, and Contravariance here.
For definitions please see https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance
Let's say there is a zoo.
abstract class Animal{}
abstract class Bird : Animal{}
abstract class Fish : Animal{}
class Dove : Bird{}
class Shark : Fish{}
The zoo is relocating, so its animals need to be moved from the old zoo to the new one.
Invariance
Before we move them, we need to put the animals into different containers. The containers all do the same operations: put an animal in it or get an animal out from it.
interface IContainer<T> where T : Animal
{
void Put(T t);
T Get(int id);
}
Obviously, for fish we need a tank:
class FishTank<T> : IContainer<T> where T : Fish
{
public void Put(T t){}
public T Get(int id){return default(T);}
}
So the fish can be put in and get out from the tank(hopefully still alive):
IContainer<Fish> fishTank = new FishTank<Fish>(); //Invariance, the two types have to be the same
fishTank.Put(new Shark());
var fish = fishTank.Get(8);
Suppose we are allowed to change it to IContainer<Animal>
, then you can accidentally put a dove in the tank, in which case tragedy will occur.
IContainer<Animal> fishTank = new FishTank<Fish>(); //Wrong, some animal can be killed
fishTank.Put(new Shark());
fishTank.Put(new Dove()); //Dove will be killed
Contravariance
To improve efficiency, the zoo management team decides to separate the load and unload process (management always does this). So we have two separate operations, one for load only, the other unload.
interface ILoad<in T> where T : Animal
{
void Put(T t);
}
Then we have a birdcage:
class BirdCage<T> : ILoad<T> where T : Bird
{
public void Put(T t)
{
}
}
ILoad<Bird> normalCage = new BirdCage<Bird>();
normalCage.Put(new Dove()); //accepts any type of birds
ILoad<Dove> doveCage = new BirdCage<Bird>();//Contravariance, Bird is less specific then Dove
doveCage.Put(new Dove()); //only accepts doves
Covariance
In the new zoo, we have a team for unloading animals.
interface IUnload<out T> where T : Animal
{
IEnumerable<T> GetAll();
}
class UnloadTeam<T> : IUnload<T> where T : Animal
{
public IEnumerable<T> GetAll()
{
return Enumerable.Empty<T>();
}
}
IUnload<Animal> unloadTeam = new UnloadTeam<Bird>();//Covariance, since Bird is more specific then Animal
var animals = unloadTeam.GetAll();
From the team's point of view, it does not matter what it is inside, they just unload the animals from the containers.