
One of the most powerful concepts in object orientated programming (OOP) is inheritance. In this tutorial we discuss inheritance and polymorphism and the capabilities and benefits it offers.
In this article:
- Preamble
- Inheritance
- Polymorphism
- Abstract Classes
- System.Object: The Root of All Classes
- Boxing and Unboxing Types
- Operator Overloading
Preamble
In previous tutorials, we demonstrated how to define our own .NET types (classes). In the real world, objects have relationships with each other. Let’s look at how we model these relationships in our code. We will focus on specialisation, which is implemented in C# through inheritance. We will also look at how instances of specialised classes can be treated as if they were instances of more general classes, a process known as polymorphism.
The ‘is-a’ relationship is one of specialisation. When we say a dog is a mammal, we mean that a dog is a specialised mammal. It has all of the characteristics of a mammal, such as giving birth to live young, breathing air, warm blooded, etc, but it specialises these characteristics with those that we associate with the dog. A cat is also a mammal, and therefore shares the generalized attributes of the mammal, but also has the specialized characteristics of cat.
These types of relationships are hierarchical because they create a relationship tree, with specialised types branching off from the more generalised ones. The higher up the hierarchy you go the more you achieve greater generalization and, conversely, the further down you go the more you achieve greater specialization.
Inheritance
Consider the following class diagram for WinForms controls, showing the relationship between the specialised classes Button
and ListBox
to the generalised class of Control
:

In C#, the specialisation relationship is generally implemented using inheritance. Button is derived from the base class Control, inheriting the characteristics and behaviours of the base class and then specialising it to its own particular needs. You create a derived class by adding a colon after the name of the derived class, followed by the name of the base class. For example:
public class Button : Control
{
}
Code language: C# (cs)
The derived class inherits all of the members of the base class, both member functions and variables. Classes in C# can only inherit from one base class (i.e. C# doesn’t support multiple inheritance like some other languages, such as C++, do).
Preventing Inheritance
When we establish base class / derived class relationships using inheritance, we are able to leverage the behaviour of existing types. However, what if you wish to define a class that cannot be inherited from? For this purpose, use the sealed
keyword. For example, suppose we don’t want to allow any derived types from the Button class. We would declare the Button class thus:
public sealed class MyButton : Control
{
}
Code language: C# (cs)
NOTE: The MyButton
control we have just declared is a custom control in our own namespace and should not be confused with the standard Button
classes provided by either the System.Windows.Forms
or System.Web.UI.WebControls
namespaces.
Now, suppose a developer tried to create a Toggle button class that inherits from our MyButton
control, like this:
public class ToggleButton : MyButton
{
}
Code language: C# (cs)
When the code project was compiled, it would result in an error something like:
'Inheritance.ToggleButton': cannot derive from sealed type 'Inheritance.MyButton'.
Code language: plaintext (plaintext)
Base-Class Constructors
In the example code (listed below), the ListBox
class has a constructor that takes three arguments. The ListBox
constructor invokes the constructor of its parent class by placing a colon after the parameter list and then invoking the base-class with the keyword base
.
public class ListBox : Control
{
public ListBox(int top, int left, string contents)
base(top, left)
{
// TODO: add the initialisation code needed.
}
}
Derived classes cannot inherit the base-class constructors; they must implement their own constructors, and can only call the base-class constructor within the definition of the derived class constructor itself. The exception to this rule is when the base-class has an accessible constructor that does not take any parameters. In this case, the base class constructor is called implicitly.
When a derived class is instantiated using a given constructor, the appropriate base-class constructor is executed before the body of the derived class constructor. It is important to be aware of this order of execution.
Protected Members
If you looked at the source code for the Control
class, you may notice the top
and left
fields are marked as protected. These fields are not accessible outside the Control
class (i.e. in the Program
class for example), but they are accessible in derived classes. The Button
class accesses the top
and left
fields from the Control
class in its DrawWindow()
method, but these fields are not declared in the Button
class itself.
Nested Types
Classes have members, and it is entirely possible for the member of a class to be another user defined type. Staying with our Control hierarchy example, the Control
class might have a member of type Location
that describes the top-left point of the control relative to its parent. Location
would then contain members of type int
.
At times, a contained class might only exist to serve its parent class and there might be no reason for it to be otherwise visible. In short, the contained inner, or nested, class acts as a helper class to the parent. Nested classes have an advantage of access to all members of the parent class, even the private members. In addition, the nested class can be marked as private. This would mean that the nested class is hidden to all but the parent class. For example, the Control class could define a nested class called Location like this:
public class Control
{
public class Location
{
}
}
Code language: C# (cs)
The above definitions make the Location
publicly visible/accessible. If we instead changed Location
‘s access modifier to protected
then only the Control
class and any derived classes would be able to access it. Alternatively, if we set the access modifier to private
then only the Control
class would be able to access and use the Location
class.
A nested class that is public is accessed within the scope of its parent class. Using the example above, we refer to Location
as Control.Location
, like this:
Control.Location position = new Control.Location();
Code language: C# (cs)
Polymorphism
Polymorphism literally means the ability to appear in many forms.
In object-orientated programming languages, such as C#, polymorphism refers to the ability to process objects differently depending on their data type or class. More specifically, it is the ability to redefine methods for derived classes.
For example, when the phone company sends your phone a ring signal, it doesn’t know what type of phone is on the other end of the line. You may have an old fashioned phone that has to energize a motor to ring a bell, or you may have a modern electronic phone that plays digital music.
As far as the phone company is concerned, it knows only about the base type of phone, and expects that any instance of a phone knows how to ring. Thus, when the phone company tells your phone to ring, it expects the phone to do the right thing. It therefore treats your individual phone polymorphically.
Creating Polymorphic Methods
Because the ListBox
class is a Control
, and the Button
class is a control, we expect to be able to use either of these types in situations that call for a control. For example, a form might want to keep a collection of all the instances of Control
it manages so that when the form is opened, it can tell each of the Control
instances to draw themselves.
For this operation to work the form doesn’t want to know which of the controls are liist boxes or buttons; the form just wants to iterate through the Control
collection and tell each one to draw. To create a polymorphic method, you need to mark the method as virtual in the base class.
For example, to indicate that the DrawWindow()
method of the Control
class is polymorphic, we add the virtual
keyword to the declaration.
public virtual void DrawWindow()
{
}
Code language: C# (cs)
In each derived class, we can implement our own version of DrawWindow()
by overriding the base class behaviour, using the override
keyword.
public override void DrawWindow()
{
// TODO: do something bespoke.
}
Code language: C# (cs)
The power of polymorphism starts becoming useful when you start accessing class members after casting them to their base-class types. Because ListBox
is derived from Control
, you are free to cast a ListBox
to a Control
type. The same goes for any Button
object, as this too derives from Control
.
C# provides a complement to method overriding: method hiding. If a derived class re-declares an identical member inherited from a base class, the derived class has hidden the parent’s member. For example, if we were to declare a new method in the Control
class called Sort()
, and then define a Sort()
method in the derived ListBox
class, then we would get a compiler warning when building.
public class Control
{
// contents omitted for brevity.
public void Sort()
{
// TODO: some sorting.
}
}
public class ListBox : Control
{
// contents omitted for brevity.
public void Sort()
{
// TODO: some sorting.
}
}
Code language: C# (cs)
The above code would generate a compiler warning like:
'Inheritance.ListBox.Sort()' hides inherited member 'Inheritance.Control.Sort()'.
Use the new keyword if hiding was intended.
Code language: plaintext (plaintext)
Although the code project still compiles, you should really resolve the warning too.
To fix this you have two options. You could update the Sort()
method in the parent class to be virtual
, and then mark the ListBox Sort()
method with an override
. The ListBox
class is then able to extend the default behaviour as required. The drawback to this is if we do not want to allow the override of the base class method, or if we can’t because the base class isn’t our own.
Also, when a virtual method is overridden, and the method is always called, even if the derived object is cast back to the base class the overridden version of the method will be executed.
The quick, and generally recommended, fix for the compiler warning is to simply do what it advises by adding a new
keyword to the method declaration, like so:
public new void Sort()
{
// TODO: some sorting.
}
Code language: C# (cs)
This explicitly tells the compiler that you are purposely hiding the base implementation and want the derived class’ version to be used.
Something to mention at this point, is how behaviours differ between using the virtual
and override
approach to using the new
keyword approach. Consider the following:
// First, if we make Control.Sort() virtual and override it in the ListBox class...
ListBox myListBox = new ListBox(4, 3, "Hello");
myListBox.Sort(); // This calls the ListBox version of the Sort() method.
Control myControl = (Control)myListBox;
myControl.Sort(); // This calls the ListBox version of the Sort() method.
// Alternatively, if we don't make Control.Sort() virtual, and then use the
// 'new' keyword in the ListBox class...
ListBox myListBox = new ListBox(4, 3, "Hello");
myListBox.Sort(); // This calls the ListBox version of the Sort() method.
Control myControl = (Control)myListBox;
myControl.Sort(); // This calls the Control version of the Sort() method.
Code language: C# (cs)
Consider the two diagrams below that demonstrate what is happening in memory.

In the top case, Control.Sort()
is declared as virtual
and is overridden in the ListBox
class. This rewires the Control
object’s Sort()
method pointer to ListBox.Sort()
in all cases.
In the bottom case, ListBox.Sort()
is just hiding the base class version of the method in the derived class. As such, casting to the Control
type returns a pointer to the Control
object’s portion of the ListBox
object and so each Sort()
method can be called independently.
Sealed Methods
The sealed
keyword can also be applied to type members to prevent virtual members from being further overridden by derived types. This is helpful when you do not wish to seal an entire class, just a few methods or properties.
For example, if we wanted to extend the Button
class but wanted to make sure that those extended classes did not further override the virtual Control.DrawWindow()
method, we can mark the override of DrawWindow()
in the Button
class as sealed
:
public class Button : Control
{
public override sealed void DrawWindow()
{
}
}
Code language: C# (cs)
Now, if we declared a ToggleButton
that inherits from Button
, and try to override the DrawWindow()
method then we would get a compiler error similar to:
'Inheritance.ToggleButton.DrawWindow()': cannot override inherited member
'Inheritance.Button.DrawWindow()' because it is sealed
Code language: plaintext (plaintext)
Abstract Classes
So far we have been looking at the Control
base class and its derived classes ListBox
and Button
. The derived classes have access to the generalised members of Control
such as top
, left
, DrawWindow()
, which the descendents then use and specialise. The thing that doesn’t make sense in this design is that you can create instances of Control
.
If you were building a windows form, would you require an instance of Control
to put on your form? It would only be the ListBox
and Button
objects that you would actually use. The only time the Control
class is required is to provide common fields/members and behaviours in the derived classes.
The Control
class is too general an entity to be instantiated,so it is better design to prevent the Control
base class from being instantiated. This can be accomplished using the abstract
keyword. For example, declaring the Control class as abstract:
public abstract class Control
{
}
Code language: C# (cs)
If we now try to instantiate Control
(e.g. Control myControl = new Control()
) then we get a compilation error, stating something like:
Cannot create an instance of the abstract class or interface 'Inheritance.Control'.
Code language: plaintext (plaintext)
The following would still be valid though:
ListBox myListBox = new ListBox(50, 100, "Wow!");
Control myControl = (Control)myControl;
myControl.DrawWindow();
Code language: C# (cs)
Abstract Methods
When a class has been defined as an abstract base class, it may define any number of abstract members. Note: marking a method as abstract means that you must mark the class as abstract.
Abstract methods can be used whenever you wish to define a method that does not supply a default implementation. This forces each derived class to override the abstract method.
In our Control
class example, every sub-class of control should implement is own version of DrawWindow()
, but nothing forces it to do so. An abstract method describes a method name and signature that must be implemented in all derived classes. For example:
public class Control
{
public abstract void DrawWindow();
}
Code language: C# (cs)
Designating DrawWindow()
as abstract
in the Control
base class has forced the derived classes to override the method. The limitation is that if we derive a class from Button
, (e.g. ToggleButton
), nothing forces that derived class to implement its own version of the DrawWindow()
method, because it will have an implementation in the Button
class.
It is worth noting that abstract classes can contain abstract and instance methods (functional methods) too. Defining instance methods in an abstract class can be useful as it enables the developer to provide default functionality in the abstract class, and defining that method as virtual
also allows derived classes to optionally override the base functionality in the usual way, if necessary.
System.Object: The Root of All Classes
All C# classes, of any type, are treated as if they ultimately derive from System.Object
. This includes all the value types too.
System.Object
provides a number of virtual methods that sub-classes can, and do, override. These are:
Equals()
. Evaluates whether two objects are equivalent. The default implementation typically compares memory address pointers for reference types, and actual values for value types.GetHashCode()
. Allows the object to provide its own has function for use in collections.GetType()
. Provides access to the ‘Type’ of the object. .NET types are self-describing so this is a simple mechanism for getting information about the Type the current object is instantiated from.ToString()
. Provides a string representation of the object. The default implementation is to return the object’s Type name, but it may be overridden in specialised classes (for example DateTime.ToString() will return a formatted date and time string).Finalise()
. Cleans up unmanaged memory resources. Implemented by the destructor.MemberwiseClone()
. Creates a shallow copy of the object. Shallow copies create a new memory reference for a new object instance, but any underlying reference types in the source object’s fields/properties the same memory address will be referenced (so changing a property in the copied object would affect the source object in the same way). To fully clone an object and all its contents, you must perform a deep copy instead.ReferenceEquals()
. A static method that evaluates whether two objects refer to the same instance (memory address reference).
Boxing and Unboxing Types
Boxing and unboxing describes the mechanisms that enable value types (e.g. integers) to be treated as reference types (objects). The value is boxed inside an object type, and subsequently unboxed into a value type.
Boxing is an implicit conversion of a value type to the object type. Boxing a value allocates an instance of the boxed type and copies the value into the new object instance. An example of this is when you pass a value type, such as an integer, into a function that is expecting a variable of type object, which is legal because int
derives from object
. This is done when using the Console.WriteLine()
method. Consider the extract from the Visual Studio IDE below, showing the intelliSense for the WriteLine
method:

The WriteLine()
method is expecting an object
(arg0), but we are going to pass in the number 2. To accommodate this, the CLR automatically boxes the value type, and the ToString()
method is called on the resulting object.
To return the boxed object back into the value type, you must explicitly unbox it. The reference type must be cast back to the value type. Consider the following example:
static void Main(string[] args)
{
// Create a value type integer.
int number = 89;
// Implicitly box the value type to a reference type.
object numberReference = number;
// Unbox the reference type back to a value type.
int unboxedNumber = (int)numberReference;
}
Code language: C# (cs)
Care should be taken when explicitly casting types, as casting to the wrong type can lead to an invalid cast exception at runtime. For example, if we try to cast the integer to a double when unboxing…
double unboxedNumber = (double)numberReference;
Code language: C# (cs)
Then we get a runtime error like this:

NOTE: the above software exception occurs at runtime
, not compile time.
Operator Overloading
It is a design goal of C# that user defined classes can have all the functionality of built-in types.
For example, say you develop a type to represent fractions. Ensuring that this class has all the functionality of the built-in types means that you must be able to perform arithmetic on instances of your fractions, and convert fractions to different types (to a float for instance).
Consider the following class, that represents a floating point number as fraction:
public class Fraction
{
public Fraction(int numerator, int denominator)
{
Numerator = numerator;
Denominator = denominator;
}
public Fraction(int number)
{
Numerator = numerator;
Denominator = 1;
}
public int Numerator { get; private set; }
public int Denominator { get; private set; }
public override string ToString()
{
return string.Format("{0} / {1}", Numerator, Denominator);
}
}
Code language: C# (cs)
This can be done by writing custom functions in your class:
// In Fraction class...
public class Fraction
{
// Existing code omitted for brevity.
public Fraction Add(Fraction first, Fraction second)
{
// Code body omitted for brevity.
}
}
// Using the method in code...
Fraction number = Fraction.Add(first, second);
Code language: C# (cs)
But, to make the above functionality look more like built-in numeric types’ arithmetic behaviour, we ideally want something like this instead:
Fraction number = first + second;
Code language: C# (cs)
The ‘operator’ Keyword
As demonstrated above, you implement operator overloads by creating static methods whose return values represent the result of an operation, and its parameters are operands. To overload the addition operator in our Fraction
example, you would declare the overload thus:
public static Fraction operator +(Fraction leftValue, Fraction rightValue)
Code language: C# (cs)
This declares an overload of the addition operator that returns a Fraction
object, and takes two arguments as its parameters. These parameters would be the left-hand-side and the right-hand-side of the addition operator. Now when we write this:
Fraction first = new Fraction(5, 6);
Fraction second = new Fraction(23, 12);
Fraction number = first + second;
Code language: C# (cs)
We can see that the overloaded +
operator would be invoked, passing the first
variable as the leftValue
parameter and the second
variable as the rightValue
parameter. When the compiler sees the above addition statement it effectively converts it to:
Fraction number = Fraction.operator+(first, second);
Code language: C# (cs)
Operator overloading can make your types more intuitive and enables them to act like other built-in types, but it can make your code unmanageable and complex. Use operator overloading when its meaning is clear and consistent with how the built-in types operate and omit them in other cases.
Implementing Operator Overloads
If we revisit our Fraction
class with the knowledge we can use operator overloading to make our Fraction act more like a built-in primitive numeric type then we might modify the class like this:
public class Fraction
{
// Existing code omitted for brevity.
public static implicit operator Fraction(int number)
{
return new Fraction(number);
}
public static explicit operator double(Fraction value)
{
return (double)value.numerator / (double)value.denominator;
}
public static Fraction operator +(Fraction leftValue, Fraction rightValue)
{
// If the denominators are the same we can just use one of them and
// add the numerators together...
if (leftValue.denominator == rightValue.denominator)
{
return new Fraction(
leftValue.numerator + rightValue.numerator,
leftValue.denominator);
}
else
{
// For different denominators, make a common denominator that is a
// product of the pair, and make the numerators a product of their
// complement's denominator.
int first = leftValue.numerator * rightValue.denominator;
int second = rightValue.numerator * leftValue.denominator;
int denominator = leftValue.denominator * rightValue.denominator;
// TODO: rationalise to lowest common denominator when you have time!
return new Fraction(first + second, denominator);
}
}
public static bool operator ==(Fraction leftValue, Fraction rightValue)
{
if (leftValue.denominator == rightValue.denominator)
{
return leftValue.numerator == rightValue.numerator;
}
else
{
// Coerce to a common denominator and then check the newly calculated numerator products.
int first = leftValue.numerator * rightValue.denominator;
int second = rightValue.numerator * leftValue.denominator;
return first == second;
}
}
public static bool operator !=(Fraction leftValue, Fraction rightValue)
{
// Simply call the == operator and invert the outcome.
return !(leftValue == rightValue);
}
}
Code language: C# (cs)
The above code demonstrates how we can provide operator overloads for different scenarios. Going top to bottom we have implemented:
- An implicit operator that can convert an
int
to aFraction
object.
// Uses 'implicit operator Fraction(int)'.
Fraction myFraction = 42;
Code language: C# (cs)
- An explicit operator for direct casting to a
double
.
Fraction myFraction = new Fraction(14, 5);
// Uses 'explicit operator double(Fraction)'.
double number = (double)myFraction;
Code language: C# (cs)
- An arithmetic operator for adding two
Fraction
objects together.
Fraction firstNumber = new Fraction(12, 7);
Fraction secondNumber = new Fraction(11, 12);
// Uses 'Fraction operator +(Fraction, Fraction)'.
Fraction result = firstNumber + secondNumber;
Code language: C# (cs)
- An equality operator for comparing two
Fraction
objects.
Fraction firstNumber = new Fraction(10, 12);
Fraction secondNumber = new Fraction(11, 12);
Fraction thirdNumber = new Fraction(5, 6);
// Uses 'bool operator ==(Fraction, Fraction)'.
bool areEqual = firstNumber == secondNumber; // Returns false.
areEqual = firstNumber == thirdNumber; // Returns true.
Code language: C# (cs)
- An inequality operator for comparing two
Fraction
objects.
Fraction firstNumber = new Fraction(4, 5);
Fraction secondNumber = new Fraction(4, 6);
Fraction thirdNumber = new Fraction(2, 3);
// Uses 'bool operator !=(Fraction, Fraction)'.
bool notEqual = firstNumber != secondNumber; // Returns true.
notEqual = secondNumber != thirdNumber; // Returns false.
Code language: C# (cs)
When overloading the equality operator (
==
), you get C# compiler warnings if you don’t override theObject.Equals
andObject.GetHashCode()
methods too.
To get rid of the compiler warnings, we can just implement the Object
virtual methods as overrides:
public class Fraction
{
// Existing code omitted for brevity.
public static bool operator ==(Fraction leftValue, Fraction rightValue)
{
// Code omitted for brevity.
}
public override bool Equals(object source)
{
if (!(source is Fraction))
{
//The supplied object is not an instance of the Fraction class.
return false;
}
// Cast the object to a fraction and use the equals operator.
return this == (Fraction)source;
}
public override int GetHashCode()
{
retirm this.denominator ^ this.numerator;
}
}
Code language: C# (cs)