In this post I present the two types of polymorphism supported by C#: Static and Dynamic Polymorphism. C#’s language features including inheritance, abstract base classes, interfaces, virtual methods, method overriding, method overloading, operator overloading, and generics, offers you a rich pallate from which you can design and create robust application architectures that support the SOLID design principles and allow you to think polymorphically.

Don’t Feel Like Reading? Watch the Video Instead!

C# supports two types of polymorphism: Static and Dynamic. Static polymorphism, otherwise, known as compile-time polymorphism, is enabled by the language features operator overloading, method overloading, and generics. Dynamic, or Runtime polymorphism, is all about method overriding and the language features that enable this are inheritance, abstract base classes, and interfaces.

Polymorphism Types in C#

The Purpose of Types

An object’s type dictates the range of allowed operations. For example, 1 + 2 = 3. That’s an (int + int). The + operator knows how to work on two integer objects. 1.5 + 2 = 3.5. That’s a (double + int), and the compiler knows how to convert the integer to a double and return a double. The next example is interesting: “1.5” + 2 results in a new string “1.52”. The + operator in this case is overloaded to perform string concatenation. In the final example, the + operator is being applied against two Person objects, p1 + p2, and will throw an exception unless the Person class overloads the + operator and defines exactly what it means in the context of two objects of type person. C# is a strongly typed language, which means the compiler needs to know the type of objects with which it’s dealing.

Purpose of Types in C#

Dynamic Polymorphism

Dynamic polymorphism, also referred to as runtime polymorphism, simply means using derived class objects where base class objects are specified. It relies on the following language features: inheritance, abstract base classes, and interfaces. Inheritance is baked into the .NET Framework. You have the System.Object class, which sits at the top of the inheritance hierarchy of all .NET Framework types. Abstract base classes are used where you only want to specify an interface or provide a partial implementation. Interfaces are used to specify the interface to an object only with no implementation. The implementation of interface methods is left to the class implementing the interface.

Dynamic Polymorphism

The difference between an interface and a class in C# is that a class can extend at most one other class, but it can implement as many interfaces as required. Note that manually checking objects for their type is not polymorphic programming.

Understanding dynamic polymorphism leads to a deeper understanding of SOLID Design Principles. These include: the Single Responsibility Principle, the Open-Closed Principle, the Liskov Substitution Principle, the Interface Segregation Principle, and the Dependency Inversion Principle.

SOLID Design Principles

Single Responsibility Principle

A class should have at most one reason to change and one responsibility.

Open-Closed Principle

A software entity should be open for extension but closed for modification.

Liskov Substitution Principle (Also see Bertrand Meyer’s Design by Contract)

Subclasses should be directly substitutable for their base classes. Or stated another way, subtypes should fulfill the contract specified by their base type’s interface and pull no surprises.

Interface Segregation Principle

Favor client-specific interfaces over one general purpose interface.

Dependency Inversion Principle

Depend upon abstractions — not upon concretions.

Fleet Simulation Architecture

The fleet simulation architecture implements a lot of the SOLID design principles. It provides three abstract base classes: Weapon, Vessel, and PowerPlant. Vessel is a composite which comprises a Weapon and a PowerPlant. There are two Vessel subclasses: Surface Ship and Submarine. There are three Weapon subclasses: FiveInchGun, CWIS, and Torpedo, and finally there are three PowerPlant subclasses: Nuclear, GasTurbine, and Steam.

Fleet Simulation Architecture
The three abstract base classes Weapon, Vessel, and PowerPlant, sit at the top of the inheritance hierarchy, and any changes made to these classes will affect the behavior of all subtypes. This is an example of the Dependency Inversion Principle (DIP). (This must be compared to historic software architectures where changes to sub-modules or sub-routines would impact the behavior of the overall application.)
Fleet Simulation Architecture

The abstract base classes form an abstraction layer, and once their interfaces have settled down, there is rarely a need to modify them. This leads to the Open-Closed Principle (OCP). Take the Weapon class for example. To create a new weapon type with different behavior and characteristics one need only extend the Weapon class to create a new subclass and provide the required implementation. In this way, the design is closed for modification but open to extension, with the notion being that modification should be avoided because of unknown system impacts and modifying code introduces new bugs. Anywhere a Weapon, Vessel, or PowerPlant is specified in the architecture, a substitution can be provided of the appropriate subtype. For example, if a method takes a Weapon as an argument, a CIWS object can be provided. The code targets the Weapon interface and CIWS is a Weapon. This is an example of the Liskov Substitution Principle (LSP) (or Bertrand Meyer’s Design by Contract). Note that a Weapon does not attempt to act like a PowerPlant. This is an example of the Interface Segregation Principle (ISP). You find many examples of the ISP throughout the .NET platform.

Person-Employee Architecture

This is an architecture for a Person-Employee application. At the top of the inheritance hierarchy sits three primary entities: A concrete class Person, an interface IPayable, and an abstract base class Employee. You can create instances of Person if you needed to. IPayable is just an interface and specifies one method Pay() which must be implemented by some class further down the inheritance hierarchy. Employee extends Person and implements IPayable, but defers the implementation of the Pay() method and leaves it to the concrete subclasses HourlyEmployee and SalariedEmployee.

Person-Employee Application Architecture

The takeaway from this architecture is how you would achieve dynamic polymorphism. If you have a reference of type Person that points to an object of type HourlyEmployee, you can only manipulate that object using the interface specified by the Person class. If you have a reference of type IPayable that points to an HourlyEmployee object, you can only call the Pay() method, as that is the interface specified by the IPayable interface. A reference of type Employee offers the most benefit, as it combines Person and IPayable, plus any additional interface members required of an Employee.

Static Polymorphism

 Static polymorphism, also referred to as compile time polymorphism, includes operator overloading, method overloading, and generics.

Operator Overloading

Operator overloading simply means defining how one of the overloadable operators behave in the context of your user-defined types. You saw earlier how the + operator was overloaded to work with different numeric types as well as serve as the string concatenation operator.

Operator Overloading

Method Overloading

Method overloading means reusing a method name to work on different types and number of arguments. You see this all the time with the System.WriteLine() method. You can overload constructor methods as well as ordinary methods.

Method Overloading

Generics

You first encounter generics in the System.Collections.Generic namespace. Example, List<T>. The ‘T’ is a type parameter. You can define generic classes and methods using type parameters. Operations on the substituted objects target the System.Object interface unless a type constraint is specified. For example:

class MyClass<T> { … } //Can’t assume anything other than System.Object
class MyClass<T> where T: Employee {…} //Ah! Expect Employee objects!

Bottom Line

Designing C# application architectures with polymorphism in mind at the very start leads to more robust, reusable, and extensible code. A deep understanding of how to achieve polymorphic behavior in all its forms is a powerful tool to add to your programmer’s toolbelt. Watch the YouTube video posted at the top of this article for coding demonstrations on both static and dynamic polymorphism in C#.