Chapter 2

Polymorphism

It’s a shapeshifter!

Subsections of Polymorphism

Introduction

The term polymorphism means many forms. In computer science, it refers to the ability of a single symbol (i.e. a function or class name) to represent multiple types. Some form of polymorphism can be found in nearly all programming languages.

While encapsulation of state and behavior into objects is the most central theoretical idea of object-oriented languages, polymorphism - specifically in the form of inheritance is a close second. In this chapter we’ll look at how polymorphism is commonly implemented in object-oriented languages.

Key Terms

Some key terms to learn in this chapter are:

  • Polymorphism
  • Type
  • Type Checking
  • Casting
  • Implicit Casting
  • Explicit Casting
  • Interface
  • Inheritance
  • Superclass
  • Subclass
  • Abstract Classes

C# Keywords:

  • Interface
  • protected
  • abstract
  • virtual
  • override
  • sealed
  • as
  • is
  • dynamic

Types

Before we can discuss polymorphism in detail, we must first understand the concept of types. In computer science, a type is a way of categorizing a variable by its storage strategy, i.e., how it is represented in the computer’s memory.

You’ve already used types extensively in your programming up to this point. Consider the declaration:

int number = 5;

The variable number is declared to have the type int. This lets the .NET interpreter know that the value of number will be stored using a specific scheme. This scheme will use 32 bits and contain the number in Two’s complement binary form. This form, and the number of bytes, allows us to represent numbers in the range -2,147,483,648 to 2,147,483,647. If we need to store larger values, we might instead use a long which uses 64 bits of storage. Or, if we only need positive numbers, we might instead use a uint, which uses 32 bits and stores the number in regular base 2 (binary) form .

This is why languages like C# provide multiple integral and float types. Each provides a different representation, representing a tradeoff between memory required to store the variable and the range or precision that variable can represent.

In addition to integral and float types, most programming languages include types for booleans, characters, arrays, and often strings. C# is no exception - you can read about its built-in value types in the documentation .

User-Defined Types

In addition to built-in types, most programming languages support user-defined types, that is, new types defined by the programmer. For example, if we were to define a C# enum:

public enum Grade {
  A,
  B,
  C,
  D,
  F
}

Defines the type Grade. We can then create variables with that type:

Grade courseGrade = Grade.A;

Similarly, structs provide a way of creating user-defined compound data types.

Classes are Types

In an object-oriented programming language, a Class also defines a new type. As we discussed in the previous chapter, the Class defines the structure for the state (what is represented) and memory (how it is represented) for objects implementing that type. Consider the C# class Student:

public class Student {
  // backing variables
  private float creditPoints = 0;
  private uint creditHours = 0;

  /// <summary>
  /// Gets and sets first name.
  /// </summary>
  public string First { get; set; }

  /// <summary>
  /// Gets and sets last name.
  /// </summary>
  public string Last { get; set; }

  /// <summary>
  /// Gets the student's GPA
  /// </summary>
  public float GPA {
    get {
      return creditPoints / creditHours;
    }
  }

  /// <summary>
  /// Adds a final grade for a course to the
  // student's GPA.
  /// </summary>
  /// <param name="grade">The student's final letter grade in the course</param>
  /// <param name="hours">The course's credit hours</param>
  public void AddCourseGrade(Grade grade, uint hours) {
    this.creditHours += hours;
    switch(grade) {
      case Grade.A:
        this.creditPoints += 4.0 * hours;
        break;
      case Grade.B:
        this.creditPoints += 3.0 * hours;
        break;
      case Grade.C:
        this.creditPoints += 2.0 * hours;
        break;
      case Grade.D:
        this.creditPoints += 1.0 * hours;
        break;
      case Grade.F:
        this.creditPoints += 0.0 * hours;
        break;
    }
  }
}

If we want to create a new student, we would create an instance of the class Student which is an object of type Student:

Student willie = new Student("Willie", "Wildcat");

Hence, the type of an object is the class it is an instance of. This is a staple across all object-oriented languages.

Static vs. Dynamic Typed Languages

A final note on types. You may hear languages being referred to as statically or dynamically typed. A statically typed language is one where the type is set by the code itself, either explicitly:

int foo = 5;

or implicitly (where the compiler determines the type based on the assigned value):

var bar = 6;

In a statically typed language, a variable cannot be assigned a value of a different type, i.e.:

foo = 8.3;

Will fail with an error, as a float is a different type than an int. Similarly, because bar has an implied type of int, this code will fail:

bar = 4.3;

However, we can cast the value to a new type (changing how it is represented), i.e.:

foo = (int)8.9;

For this to work, the language must know how to perform the cast. The cast may also lose some information - in the above example, the resulting value of foo is 8 (the fractional part is discarded).

In contrast, in a dynamically typed language the type of the variable changes when a value of a different type is assigned to it. For example, in JavaScript, this expression is legal:

var a = 5;
a = "foo";

and the type of a changes from int (at the first assignment) to string (at the second assignment).

C#, Java, C, C++, and Kotlin are all statically typed languages, while Python, JavaScript, and Ruby are dynamically typed languages.

Interfaces

If we think back to the concept of message passing in object-oriented languages, it can be useful to think of the collection of public methods available in a class as an interface, i.e., a list of messages you can dispatch to an object created from that class. When you were first learning a language (and probably even now), you find yourself referring to these kinds of lists, either in the language documentation, or via Intellisense in Visual Studio.

The Java API The Java API

Visual Studio Intellisense Visual Studio Intellisense

Essentially, programmers use these ‘interfaces’ to determine what methods can be invoked on an object. In other words, which messages can be passed to the object. This ‘interface’ (note the lowercase i) is determined by the class definition, specifically what methods it contains.

In dynamically typed programming languages, like Python, JavaScript, and Ruby, if two classes accept the same message, you can treat them interchangeably, i.e. the Kangaroo class and Car class both define a jump() method, you could populate a list with both, and call the jump() method on each:

var jumpables = [new Kangaroo(), new Car(), new Kangaroo()];
for(int i = 0; i < jumpables.length; i++) {
    jumpables[i].jump();
}

This is sometimes called duck typing , from the sense that “if it walks like a duck, and quacks like a duck, it might as well be a duck.”

However, for statically typed languages we must explicitly indicate that two types both possess the same message definition, by making the interface explicit. We do this by declaring an interface. I.e., the interface for classes that possess a parameter-less jump method might be:

/// <summary>
/// An interface indicating an object's ability to jump
/// </summary>
public interface IJumpable {
    
    /// <summary>
    /// A method that causes the object to jump
    /// </summary>
    void Jump();
}

In C#, it is common practice to preface interface names with the character I. The interface declaration defines an ‘interface’ - the shape of the messages that can be passed to an object implementing the interface - in the form of a method signature. Note that this signature does not include a body, but instead ends in a semicolon (;). An interface simply indicates the message to be sent, not the behavior it will cause! We can specify as many methods in an interface declaration as we want.

Also note that the method signatures in an interface declaration do not have access modifiers. This is because the whole purpose of defining an interface is to signify methods that can be used by other code. In other words, public access is implied by including the method signature in the interface declaration.

This interface can then be implemented by other classes by listing it after the class name, after a colon :. Any Class declaration implementing the interface must define public methods whose signatures match those were specified by the interface:

/// <summary>A class representing a kangaroo</summary>
public class Kangaroo : IJumpable 
{
    /// <summary>Causes the Kangaroo to jump into the air</summary>
    public void Jump() {
        // TODO: Implement jumping...
    }
}

/// <summary>A class representing an automobile</summary>
public class Car : IJumpable 
{
    /// <summary>Helps a stalled car to start by providing electricity from another car's battery</summary>
    public void Jump() {
        // TODO: Implement jumping a car...
    }

    /// <summary>Starts the car</summary>
    public void Start() {
        // TODO: Implement starting a car...
    }
}

We can then treat these two disparate classes as though they shared the same type, defined by the IJumpable interface:

List<IJumpable> jumpables = new List<IJumpable>() {new Kangaroo(), new Car(), new Kangaroo()};
for(int i = 0; i < jumpables.Count; i++)
{
    jumpables[i].Jump();
}

Note that while we are treating the Kangaroo and Car instances as IJumpable instances, we can only invoke the methods defined in the IJumpable interface, even if these objects have other methods. Essentially, the interface represents a new type that can be shared amongst disparate objects in a statically-typed language. The interface definition serves to assure the static type checker that the objects implementing it can be treated as this new type - i.e. the Interface provides a mechanism for implementing polymorphism.

We often describe the relationship between the interface and the class that implements it as a is-a relationship, i.e. a Kangaroo is an IJumpable (i.e. a Kangaroo is a thing that can jump). We further distinguish this from a related polymorphic mechanism, inheritance, by the strength of the relationship. We consider the relationship between interfaces and classes implementing them to be weak is-a connections. For example, other than the shared interface, a Kangaroo and a Car don’t have much to do with one another.

A C# class can implement as many interfaces as we want, they just need to be separated by commas, i.e.:

public class Frog : IJumpable, ICroakable, ICatchFlies
{
    // TODO: Implement frog class...
}

Object Inheritance

In an object-oriented language, inheritance is a mechanism for deriving part of a class definition from another existing class definition. This allows the programmer to “share” code between classes, reducing the amount of code that must be written.

Consider a Student class:

/// <summary>
/// A class representing a student
/// </summary>
public class Student {

  // private backing variables
  private double hours;
  private double points;

  /// <summary>
  /// Gets the students' GPA
  /// </summary>
  public double GPA {
    get {
      return points / hours;
    }
  }

  /// <summary>
  /// Gets or sets the first name
  /// </summary>
  public string First { get; set; }

  /// <summary>
  /// Gets or sets the last name
  /// </summary>
  public string Last { get; set; }

  /// <summary>
  /// Constructs a new instance of Student
  /// </summary>
  /// <param name="first">The student's first name </param>
  /// <param name="last">The student's last name</param>
  public Student(string first, string last) {
    this.First = first;
    this.Last = last;
  }

  /// <summary>
  /// Adds a new course grade to the student's record.
  /// </summary>
  /// <param name="creditHours">The number of credit hours in the course </param>
  /// <param name="finalGrade">The final grade earned in the course</param>
  ///
  public void AddCourseGrade(uint creditHours, Grade finalGrade) {
    this.hours += creditHours;
    switch(finalGrade) {
      case Grade.A:
        this.points += 4 * creditHours;
        break;
      case Grade.B:
        this.points += 3 * creditHours;
        break;
      case Grade.C:
        this.points += 2 * creditHours;
        break;
      case Grade.D:
        this.points += 1 * creditHours;
        break;
    }
  }
}

This would work well for representing a student. But what if we are representing multiple kinds of students, like undergraduate and graduate students? We’d need separate classes for each, but both would still have names and calculate their GPA the same way. So it would be handy if we could say “an undergraduate is a student, and has all the properties and methods a student has” and “a graduate student is a student, and has all the properties and methods a student has.” This is exactly what inheritance does for us, and we often describe it as a is a relationship. We distinguish this from the interface mechanism we looked at earlier by saying it is a strong is a relationship, as an Undergraduate student is, for all purposes, also a Student.

Let’s define an undergraduate student class:

/// <summary>
/// A class representing an undergraduate student
/// </summary>
public class UndergraduateStudent : Student {

  /// <summary>
  /// Constructs a new instance of UndergraduateStudent
  /// </summary>
  /// <param name="first">The student's first name </param>
  /// <param name="last">The student's last name</param>
  public UndergraduateStudent(string first, string last) : base(first, last) {
  }
}

In C#, the : indicates inheritance - so public class UndergraduateStudent : Student indicates that UndergraduateStudent inherits from (is a) Student. Thus, it has properties First, Last, and GPA that are inherited from Student. Similarly, it inherits the AddCourseGrade() method.

In fact, the only method we need to define in our UndergraduateStudent class is the constructor - and we only need to define this because the base class has a defined constructor taking two parameters, first and last names. This Student constructor must be invoked by the UndergraduateStudent constructor - that’s what the :base(first, last) portion does - it invokes the Student constructor with the first and last parameters passed into the UndergraduateStudent constructor.

Inheritance, State, and Behavior

Let’s define a GraduateStudent class as well. This will look much like an UndergraduateStudent, but all graduates have a bachelor’s degree:

/// <summary>
/// A class representing a graduate student
/// </summary>
public class GraduateStudent : Student {

  /// <summary>
  /// Gets the student's bachelor degree
  /// </summary>
  public string BachelorDegree {
    get; private set;
  }

  /// <summary>
  /// Constructs a new instance of GraduateStudent
  /// </summary>
  /// <param name="first">The student's first name </param>
  /// <param name="last">The student's last name</param>
  /// <param name="degree">The student's bachelor degree</param>
  public GraduateStudent(string first, string last, string degree) : base(first, last) {
    BachelorDegree = degree;
  }
}

Here we add a property for BachelorDegree. Since it’s setter is marked as private, it can only be written to by the class, as is done in the constructor. To the outside world, it is treated as read-only.

Thus, the GraduateStudent has all the state and behavior encapsulated in Student, plus the additional state of the bachelor’s degree title.

The protected Keyword

What you might not expect is that any fields declared private in the base class are inaccessible in the derived class. Thus, the private fields points and hours cannot be used in a method defined in GraduateStudent. This is again part of the encapsulation and data hiding ideals - we’ve encapsulated and hidden those variables within the base class, and any code outside that assembly, even in a derived class, is not allowed to mess with it.

However, we often will want to allow access to such variables in a derived class. C# uses the access modifier protected to allow for this access in derived classes, but not the wider world.

Inheritance and Memory

What happens when we construct an instance of GraduateStudent? First, we invoke the constructor of the GraduateStudent class:

GraduateStudent bobby = new GraduateStudent("Bobby", "TwoSocks", "Economics");

This constructor then invokes the constructor of the base class, Student, with the arguments "Bobby" and "TwoSocks". Thus, we allocate space to hold the state of a student, and populate it with the values set by the constructor. Finally, execution returns to the derived class of GraduateStudent, which allocates the additional memory for the reference to the BachelorDegree property. Thus, the memory space of the GraduateStudent contains an instance of the Student, somewhat like nesting dolls.

Because of this, we can treat a GraduateStudent object as a Student object. For example, we can store it in a list of type Student, along with UndergraduateStudent objects:

List<Student> students = new List<Student>();
students.Add(bobby);
students.Add(new UndergraduateStudent("Mary", "Contrary"));

Because of their relationship through inheritance, both GraduateStudent class instances and UndergraduateStudent class instances are considered to be of type Student, as well as their supertypes.

Nested Inheritance

We can go as deep as we like with inheritance - each base type can be a superclass of another base type, and has all the state and behavior of all the inherited base classes.

This said, having too many levels of inheritance can make it difficult to reason about an object. In practice, a good guideline is to limit nested inheritance to two or three levels of depth.

Abstract Classes

If we have a base class that only exists to inherit from (like our Student class in the example), we can mark it as abstract with the abstract keyword. An abstract class cannot be instantiated (that is, we cannot create an instance of it using the new keyword). It can still define fields and methods, but you can’t construct it. If we were to re-write our Student class as an abstract class:

/// <summary>
/// A class representing a student
/// </summary>
public abstract class Student {

  // private backing variables
  private double hours;
  private double points;

  /// <summary>
  /// Gets the students' GPA
  /// </summary>
  public double GPA {
    get {
      return points / hours;
    }
  }

  /// <summary>
  /// Gets or sets the first name
  /// </summary>
  public string First { get; set; }

  /// <summary>
  /// Gets or sets the last name
  /// </summary>
  public string Last { get; set; }

  /// <summary>
  /// Constructs a new instance of Student
  /// </summary>
  /// <param name="first">The student's first name </param>
  /// <param name="last">The student's last name</param>
  public Student(string first, string last) {
    this.First = first;
    this.Last = last;
  }

  /// <summary>
  /// Adds a new course grade to the student's record.
  /// </summary>
  /// <param name="creditHours">The number of credit hours in the course </param>
  /// <param name="finalGrade">The final grade earned in the course</param>
  ///
  public void AddCourseGrade(uint creditHours, Grade finalGrade) {
    this.hours += creditHours;
    switch(finalGrade) {
      case Grade.A:
        this.points += 4 * creditHours;
        break;
      case Grade.B:
        this.points += 3 * creditHours;
        break;
      case Grade.C:
        this.points += 2 * creditHours;
        break;
      case Grade.D:
        this.points += 1 * creditHours;
        break;
    }
  }
}

Now with Student as an abstract class, attempting to create a Student instance i.e. Student mark = new Student("Mark", "Guy") would fail with an exception. However, we can still create instances of the derived classes GraduateStudent and UndergraduateStudent, and treat them as Student instances. It is best practice to make a class abstract if it serves only as a base class for derived classes and will never be created directly.

Sealed Classes

Conversely, C# also offers the sealed keyword, which can be used to indicate that a class should not be inheritable. For example:

/// <summary>
/// A class that cannot be inherited from
/// </summary>
public sealed class DoNotDerive {

}

Derived classes can also be sealed. I.e., we could seal our UndergraduateStudent class to prevent further derivation:

/// <summary>
/// A sealed version  of the class representing an undergraduate student
/// </summary>
public sealed class UndergraduateStudent : Student {

  /// <summary>
  /// Constructs a new instance of UndergraduateStudent
  /// </summary>
  /// <param name="first">The student's first name </param>
  /// <param name="last">The student's last name</param>
  public UndergraduateStudent(string first, string last) : base(first, last) {
  }
}

Many of the library classes provided with the C# installation are sealed. This helps prevent developers from making changes to well-known classes that would make their code harder to maintain. It is good practice to seal classes that you expect will never be inherited from.

Interfaces and Inheritance

A class can use both inheritance and interfaces. In C#, a class can only inherit one base class, and it should always be the first after the colon (:). Following that we can have as many interfaces as we want, all separated from each other and the base class by commas (,):

public class UndergraduateStudent : Student, ITeachable, IEmailable 
{
  // TODO: Implement student class 
}

Casting

You have probably used casting to convert numeric values from one type to another, i.e.:

int a = 5;
double b = a;

And

int c = (int)b;

What you are actually doing when you cast is transforming a value from one type to another. In the first case, you are taking the value of a (5), and converting it to the equivalent double (5.0). If you consider the internal representation of an integer (a 2’s complement binary number) to a double (an IEEE 754 standard representation), we are actually applying a conversion algorithm to the binary representations.

We call the first operation an implicit cast, as we don’t expressly tell the compiler to perform the cast. In contrast, the second assignment is an explicit cast, as we signify the cast by wrapping the type we are casting to in parenthesis before the variable we are casting. We have to perform an explicit cast in the second case, as the conversion has the possibility of losing some precision (i.e. if we cast 7.2 to an integer, it would be truncated to 7). In any case where the conversion may lose precision or possibly throw an error, an explicit cast is required.

Custom Casting Conversions

We can actually extend the C# language to add additional conversions to provide additional casting operations. Consider if we had Rectangle and Square structs:

/// <summary>A struct representing a rectangle</summary>
public struct Rectangle {
    
    /// <summary>The length of the short side of the rectangle</summary>
    public int ShortSideLength;
    
    /// <summary>The length of the long side of the rectangle</summary>
    public int LongSideLength;
    
    /// <summary>Constructs a new rectangle</summary>
    /// <param name="shortSideLength">The length of the shorter sides of the rectangle</param>
    /// <param name="longSideLength">The length of the longer sides of the rectangle</param>
    public Rectangle(int shortSideLength, int longSideLength){
        ShortSideLength = shortSideLength;
        LongSideLength = longSideLength;
    }
}

/// <summary>A struct representing a square</summary>
public struct Square {

    /// <summary> The length of the square's sides</summary>
    public int SideLength;

    /// <summary>Constructs a new square</summary>
    /// <param name="sideLength">The length of the square's sides</param>
    public Square(int sideLength){
        SideLength = sideLength;
    }
}

Since we know that a square is a special case of a rectangle (where all sides are the same length), we might define an implicit casting operator to convert it into a Rectangle (this would be placed inside the Square struct definition):

    /// <summary>Casts the <paramref name="square"/> into a Rectangle</summary>
    /// <param name="square">The square to cast</param>
    public static implicit operator Rectangle(Square square) 
    {
        return new Rectangle(square.SideLength, square.SideLength);
    }

Similarly, we might create a cast operator to convert a rectangle to a square. But as this can only happen when the sides of the rectangle are all the same size, it would need to be an explicit cast operator , and throw an exception when that condition is not met (this method is placed in the Rectangle struct definition):

    /// <summary>Casts the <paramref name="rectangle"/> into a Square</summary>
    /// <param name="rectangle">The rectangle to cast</param>
    /// <exception cref="System.InvalidCastOperation">The rectangle sides must be equal to cast to a square</exception>
    public static explicit operator Square(Rectangle rectangle){
        if(rectangle.LongSideLength != rectangle.ShortSideLength) throw new InvalidCastException("The sides of a square must be of equal lengths");
        return new Square(rectangle.LongSideLength);
    }

Casting and Inheritance

Casting becomes a bit more involved when we consider inheritance. As you saw in the previous discussion of inheritance, we can treat derived classes as the base class, i.e. the code:

Student sam = new UndergraduateStudent("Sam", "Malone");

Is actually implicitly casting the undergraduate student “Sam Malone” into a student class. Because an UndergraduateStudent is a Student, this cast can be implicit. Moreover, we don’t need to define a casting operator - we can always implicitly cast a class to one of its ancestor classes, it’s built into the inheritance mechanism of C#.

Going the other way requires an explicit cast as there is a chance that the Student we are casting isn’t an undergraduate, i.e.:

UndergraduateStudent u = (UndergraduateStudent)sam;

If we tried to cast sam into a graduate student:

GraduateStudent g = (GraduateStudent)sam;

The program would throw an InvalidCastException when run.

Casting and Interfaces

Casting interacts similarly with interfaces. A class can be implicitly cast to an interface it implements:

IJumpable roo = new Kangaroo();

But must be explicitly cast to convert it back into the class that implemented it:

Kangaroo k = (Kangaroo)roo;

And if that cast is illegal, we’ll throw an InvalidCastException:

Car c = (Car)roo;

The as Operator

When we are casting reference and nullable types, we have an additional casting option - the as casting operator.

The as operator performs the cast, or evaluates to null if the cast fails (instead of throwing an InvalidCastException), i.e.:

UndergraduateStudent u = sam as UndergraduateStudent; // evaluates to an UndergraduateStudent 
GraduateStudent g = sam as GraduateStudent; // evaluates to null
Kangaroo k = roo as Kangaroo; // evaluates to a Kangaroo 
Car c = roo as Kangaroo; // evaluates to null

The is Operator

Rather than performing a cast and catching the exception (or performing a null check when using the as operator), it is often useful to know if a cast is possible. This can be checked for with the is operator. It evaluates to a boolean, true if the cast is possible, false if not:

sam is UndergraduateStudent; // evaluates to true
sam is GraduateStudent; // evaluates to false
roo is Kangaroo; // evaluates to true
roo is Car; // evaluates to false
Warning

The is operator does not work with user-defined casting operators, i.e. when used with the Rectangle/Square cast we defined above:

Square s = new Square(10);
bool test = s is Rectangle;

The value of test will be false, even though we have a user-defined implicit cast that works.

The is operator is commonly used to determine if a cast will succeed before performing it, i.e.:

if(sam is UndergraduateStudent) 
{
    Undergraduate samAsUGrad = sam as UndergraduateStudent;
    // TODO: Do something undergraduate-ey with samAsUGrad
}

This pattern was so commonly employed, it led to the addition of the is type pattern matching expression in C# version 7.0:

if(sam is UndergraduateStudent samAsUGrad) 
{
    // TODO: Do something undergraduate-y with samAsUGrad
}

If the cast is possible, it is performed and the result assigned to the provided variable name (in this case, samAsUGrad). This is another example of syntactic sugar .

Message Dispatching

The term dispatch refers to how a language decides which polymorphic operation (a method or function) a message should trigger.

Consider polymorphic functions in C# (aka Method Overloading, where multiple methods use the same name but have different parameters) like this one for calculating the rounded sum of an array of numbers:

int RoundedSum(int[] a) {
  int sum = 0;
  foreach(int i in a) {
    sum += i;
  }
  return sum;
}

int RoundedSum(float[] a) {
    double sum = 0;
    foreach(int i in a) {
        sum += i;
    }
    return (int)Math.Round(sum);
}

How does the interpreter know which version to invoke at runtime? It should not be a surprise that it is determined by the arguments - if an integer array is passed, the first is invoked, if a float array is passed, the second.

Object-Oriented Polymorphism

However, inheritance can cause some challenges in selecting the appropriate polymorphic form. Consider the following fruit implementations that feature a Blend() method:

/// <summary>
/// A base class representing fruit
/// </summary>
public class Fruit
{
    /// <summary>
    /// Blends the fruit
    /// </summary>
    /// <returns>The result of blending</returns>
    public string Blend() {
      return "A pulpy mess, I guess";
    }
}

/// <summary>
/// A class representing a banana
/// </summary>
public class Banana : Fruit
{
    /// <summary>
    /// Blends the banana
    /// </summary>
    /// <returns>The result of blending the banana</returns>
    public string Blend()
    {
        return "yellow mush";
    }
}

/// <summary>
/// A class representing a Strawberry
/// </summary>
public class Strawberry : Fruit
{
    /// <summary>
    /// Blends the strawberry
    /// </summary>
    /// <returns>The result of blending a strawberry</returns>
    public string Blend()
    {
        return "Gooey Red Sweetness";
    }
}

Let’s add fruit instances to a list, and invoke their Blend() methods:

List<Fruit> toBlend = new List<Fruit>();
toBlend.Add(new Banana());
toBlend.Add(new Strawberry());

foreach(Fruit item in toBlend) {
  Console.WriteLine(item.Blend());
}

You might expect this code to produce the lines:

yellow mush
Gooey Red Sweetness

As these are the return values for the Blend() methods for the Banana and Strawberry classes, respectively. However, we will get:

A pulpy mess, I guess?
A pulpy mess, I guess?

Which is the return value for the Fruit base class Blend() implementation. The line forEach(Fruit item in toBlend) explicitly tells the interpreter to treat the item as a Fruit instance, so of the two available methods (the base or super class implementation), the Fruit base class one is selected.

C# 4.0 introduced a new keyword, dynamic to allow variables like item to be dynamically typed at runtime. Hence, changing the loop to this:

forEach(dynamic item in toBlend) {
  Console.WriteLine(item.Blend());
}

Will give us the first set of results we discussed.

Method Overriding

Of course, part of the issue in the above example is that we actually have two implementations for Blend() available to each fruit. If we wanted all bananas to use the Banana class’s Blend() method, even when the banana was being treated as a Fruit, we need to override the base method instead of creating a new one that hides it (in fact, in Visual Studio we should get a warning that our new method hides the base implementation, and be prompted to add the new keyword if that was our intent).

To override a base class method, we first must mark it as abstract or virtual. The first keyword, abstract, indicates that the method does not have an implementation (a body). The second, virtual, indicates that the base class does provide an implementation. We should use abstract when each derived class will define its own implementation, and virtual when some derived classes will want to use a common base implementation. Then, we must mark the method in the derived class with the override keyword.

Considering our Fruit class, since we’re providing a unique implementation of Blend() in each derived class, the abstract keyword is more appropriate:

/// <summary>
/// A base class representing fruit
/// </summary>
public abstract class Fruit : IBlendable
{
    /// <summary>
    /// Blends the fruit
    /// </summary>
    /// <returns>The result of blending</returns>
    public abstract string Blend();
}

As you can see above, the Blend() method does not have a body, only the method signature.

Also, note that if we use an abstract method in a class, the class itself must also be declared abstract. The reason should be clear - an abstract method cannot be called, so we should not create an object that only has the abstract method. The virtual keyword can be used in both abstract and regular classes.

Now we can override the Blend() method in Banana class:

/// <summary>
/// A class representing a banana
/// </summary>
public class Banana : Fruit
{
    /// <summary>
    /// Blends the banana
    /// </summary>
    /// <returns>The result of blending the banana</returns>
    public override string Blend()
    {
        return "yellow mush";
    }
}

Now, even if we go back to our non-dynamic loop that treats our fruit as Fruit instances, we’ll get the result of the Banana class’s Blend() method.

We can override any method marked abstract, virtual, or override (this last will only occur in a derived class whose base class is also derived, as it is overriding an already-overridden method).

Sealed Methods

We can also apply the sealed keyword to overridden methods, which prevents them from being overridden further. Let’s apply this to the Strawberry class:

/// <summary>
/// A class representing a Strawberry
/// </summary>
public class Strawberry : Fruit
{
    /// <summary>
    /// Blends the strawberry
    /// </summary>
    /// <returns>The result of blending a strawberry</returns>
    public sealed override string Blend()
    {
        return "Gooey Red Sweetness";
    }
}

Now, any class inheriting from Strawberry will not be allowed to override the Blend() method.

C# Collections

Collections in C# are a great example of polymorphism in action. Many collections utilize generics to allow the collection to hold an arbitrary type. For example, the List<T> can be used to hold strings, integers, or even specific objects:

List<string> strings = new List<string>();
List<int> ints = new List<int>();
List<Person> persons = new List<Person>();

We can also use an interface as the type, as we did with the IJumpable interface as we discussed in the generics section , i.e.:

List<IJumpable> jumpables = new List<IJumpable>();
jumpables.Add(new Kangaroo());
jumpables.Add(new Car());
jumpables.Add(new Kangaroo());

Collection Interfaces

The C# language and system libraries also define a number of interfaces that apply to custom collections. Implementing these interfaces allows different kinds of data structures to be utilized in a standardized way.

The IEnumerable<T> Interface

The first of these is the IEnumerable<T> interface, which requires the collection to implement one method:

  • public IEnumerator<T> GetEnumerator()

Implementing this interface allows the collection to be used in a foreach loop.

The ICollection<T> Interface

C# Collections also typically implement the ICollection<T> interface, which extends the IEnumerable<T> interface and adds additional methods:

  • public void Add<T>(T item) adds item to the collection
  • public void Clear() empties the collection
  • public bool Contains(T item) returns true if item is in the collection, false if not.
  • public void CopyTo(T[] array, int arrayIndex) copies the collection contents into array, starting at arrayIndex.
  • public bool Remove(T item) removes item from the collection, returning true if item was removed, false otherwise

Additionally, the collection must implement the following properties:

  • int Count the number of items in the collection
  • bool IsReadOnly the collection is read-only (can’t be added to or removed from)

The IList<T> Interface

Finally, collections that have an implied order and are intended to be accessed by a specific index should probably implement the IList<T> interface, which extends ICollection<T> and IEnumerable<T>. This interface adds these additional methods:

  • public int IndexOf(T item) returns the index of item in the list, or -1 if not found
  • public void Insert(int index, T item) Inserts item into the list at position index
  • public void RemoveAt(int index) Removes the item from the list at position index

The interface also adds the property:

  • Item[int index] which gets or sets the item at index.

Collection Implementation Strategies

When writing a C# collection, there are three general strategies you can follow to ensure you implement the corresponding interfaces:

  1. Write the entire class by scratch
  2. Implement the interface methods as a pass-through to a system library collection
  3. Inherit from a system library collection

Writing collections from scratch was the strategy you utilized in CIS 300 - Data Structures and Algorithms. While this strategy gives you the most control, it is also the most time-consuming.

The pass-through strategy involves creating a system library collection, such as a List<T>, as a private field in your collection class. Then, when you implement the necessary interface methods, you simply pass through the call to the private collection. I.e.:

public class PassThroughList<T> : IList<T>
{
  private List<T> _list = new List<T>;

  public IEnumerator<T> GetEnumerator() 
  {
    return _list.GetEnumerator();
  } 

  // TODO: Implement remaining methods and properties...
}

Using this approach, you can add whatever additional logic your collection needs into your pass-through methods without needing to re-implement the basic collection functionality.

Using inheritance gives your derived class all of the methods of the base class, so if you extend a class that already implements the collection interfaces, you’ve already got all the methods!

public class InheritedList<T> : List<T>
{
  // All IList<T>, ICollection<T>, and IEnumerable<T> methods 
  // from List<T> are already defined on InheritedList<T>
}

However, most system collection class methods are not declared as virtual, so you cannot override them to add custom functionality.

Summary

In this chapter, we explored the concept of types and discussed how variables are specific types that can be explicitly or implicitly declared. We saw how in a statically-typed language (like C#), variables are not allowed to change types (though they can do so in a dynamically-typed language). We also discussed how casting can convert a value stored in a variable into a different type. Implicit casts can happen automatically, but explicit casts must be indicated by the programmer using a cast operator, as the cast could result in loss of precision or the throwing of an exception.

We explored how class declarations and interface declarations create new types. We saw how polymorphic mechanisms like interface implementation and inheritance allow objects to be treated as (and cast to) different types. We also introduced the as and is casting operators, which can be used to cast or test the ability to cast, respectively. We saw that if the as cast operator fails, it evaluates to null instead of throwing an exception. We also saw the is type pattern expression, which simplifies a casting test and casting operation into a single expression.

Next, we looked at how C# collections leverage the use of interfaces, inheritance, and generic types to quickly and easily make custom collection objects that interact with the C# language in well-defined ways.

Finally, we explored how messages are dispatched when polymorphism is involved. We saw that the method invoked depends on what Type we are currently treating the object as. We saw how the C# modifiers protected, abstract, virtual, override, and sealed interacted with this message dispatch process. We also saw how the dynamic type could delay determining an object’s type until runtime.