This paper by Luca Cardelli and Peter Wegner is a great explanation of why types exist, what types of polymorphism exist, and seems to be up-to-date with the object oriented languages that have come out (which use subtyping for polymorphism, in this paper as a type of universal polymorphism named: inclusion).

Why care?

There have been multiple times now where I've been programming in C#, and I have expectations which don't match what the type system is capable of. Here is a silly example:

public class Bird
  private bool isFlying = false;
  public Bird Fly() {
    isFlying = true;
    return this;

public class Duck : Bird
  private bool isSwimming = false;
  public Duck Swim() {
    isFlying = false;
    isSwimming = true;

If I wanted to use the Fly() function, I would be returned a Bird. This doesn't matter if I call Fly() from an instance of Duck, I would still get a Bird back. This could cause issues and nasty type casts if I wanted to make my Duck fly, then go for a swim. I certainly wouldn't want to re-write 'Fly' for every subclass either (defeats the purpose of all of this subclassing). I'm not sure if there is a way to indicate to the base class that the Fly function should return the exact type, instead of always using Bird.

More behavior that I'd like to understand better is the relationship between IEnumerable<T>, List<T>, and the popular LINQ functions: Select, Where, etc… It seems like all of the LINQ function return IEnumerable, and therefore, default to their 'lazy evaluation'. This has messed with me in the real world multiple times. Why could List<T> when called with Select(…) not just know that it should remain a list, and not return an IEnumerable<T> from Select(…)? This really seems to tamper with the abstraction of what IEnumerable is. From what I see, IEnumerable represents something which may be enumerated. Since a List<T> is something which already has been enumerated before, why would it make sense to place the list back into an IEnumerable after calling Select(…)? Sure, I suppose you could enumerate something over and over, but that's wasteful with no benefit (unless that is what you need to do, in which case you should START with an IEnumerable… and not a List<T> anyway).

Maybe both of these examples are due to C# lacking in flexibility with polymorphism? Lets find out 😄

Adhoc Polymorphism