Now that we've discussed most of the basics we need for a C# program, let's talk about two concepts that are central to how C# (and indeed, all object-oriented programming languages) work: inheritance and polymorphism.
The Sample Project
Inheritance
Inheritance allows a class to reuse the properties, methods, and behavior of another class, and to extend or modify that behavior.
The class which implements the original properties or methods and will be inherited from is called the base class; the class which inherits from the base class is called the derived class. A derived class inherits from a base class.
"Is A" and "Is A Kind Of"
When talking about inheritance, we normally think of the derived classes having an "is a" or "is a kind of" relationship with the base class.
For example, a bee is an insect, a Toyota Corolla is a car, and a dresser is a kind of furniture. In these examples, Insect, Car, and Furniture are the base classes, while Bee, Toyota Corolla, and Dresser are the derived classes.
public class Insect { /*...*/ }
public class Bee : Insect { /*...*/ }
public class Car { /*...*/ }
public class ToyotaCorolla : Car { /*...*/ }
public class Furniture { /*...*/ }
public class Dresser : Furniture { /*...*/ }
In C#, we specify that an object inherits from another object using the :
operator, as shown above.
You can have multiple distinct classes inherit from a common base class (this is, in fact, a defining trait of inheritance). In the code below, the derived classes Dog
and Wolf
both inherit from the base class Animal
.
public class Animal //Base class
{
public string SpeciesName { get; set; }
public bool IsDomesticated { get; set; }
public virtual void MakeSound()
{
Console.WriteLine("Basic Animal Sound");
}
}
public class Dog : Animal //Derived class
{
public string BreedName { get; set; }
public Dog(string breedName)
{
SpeciesName = "Canis familiaris";
IsDomesticated = true;
BreedName = breedName;
}
public override void MakeSound()
{
Console.WriteLine("Bark!");
}
}
public class Wolf : Animal //Derived class
{
public Wolf()
{
SpeciesName = "Canis lupus";
IsDomesticated = false;
}
public override void MakeSound()
{
Console.WriteLine("Awooooooooo!");
}
}
Elsewhere in our code, we can create instances of Dog
and Wolf
and call their respective MakeSound()
methods.
Dog dog = new Dog("Labrador Retriever");
dog.MakeSound();
Wolf wolf = new Wolf();
wolf.MakeSound();
Access Modifiers
Properties and methods in the base class have access modifiers that change whether the derived classes can use them.
private
members are not visible by derived classes.internal
members are only visible by derived classes if they are in the same assembly as the base class.protected
members are ONLY visible in derived classes.public
members are visible to all code.
Overriding Methods
Derived classes can also change the implementation of the base class's methods. The derived class's implementation for said methods is used in place of the original implementation, which will not be executed.
In order to do this, the method in the base class must be marked with the virtual
keyword, and the methods in the derived class must have the same name and parameters and be marked with the override
keyword.
public class Animal
{
public virtual void MakeSound() { /*...*/ }
}
public class Tiger : Animal
{
public override void MakeSound()
{
Console.WriteLine("Roar!");
}
}
public class Hobbes : Animal
{
public override void MakeSound()
{
Console.WriteLine("Denial springs eternal.");
}
}
Base Constructors
If the derived class's constructor needs to call a constructor in the base class, they can do so using the base
keyword.
public class Animal
{
public string SpeciesName { get; set; }
public Animal(string speciesName)
{
SpeciesName = speciesName;
}
}
public class BlueWhale : Animal
{
public BlueWhale() : base("Balaenoptera musculus") { /*...*/ }
}
Implicit Inheritance
We know from the first post in this series that all C# classes inherit from a base type called System.Object
. Due to inheritance, this also means that all objects ever instantiated in C# can all use methods defined in System.Object
.
Let's define an new, empty Vegetable
class and then create an instance of it to show what we mean:
public class Vegetable { }
Vegetable myVegetable = new Vegetable();
var stringDescription = myVegetable.ToString(); //Method implemented in System.Object
var type = myVegetable.GetType(); //Method implemented in System.Object
Because all objects in C# must have a type, and all objects in C# inherit from System.Object
, the class Vegetable
will be able to use methods defined on System.Object
. This is called implicit inheritance.
All objects in C# can implement these common methods, including ToString()
, GetType()
, Equals()
, GetHashCode()
, MemberwiseClone()
and more!
No Multiple Inheritance!
C# does not permit multiple inheritance; a single C# class cannot inherit from multiple other classes. There are other ways to inherit behavior, though, and the next post in this series will cover two of them: interfaces and abstract classes.
Polymorphism
Polymorphism, along with encapsulation and inheritance, are the three defining characteristics of object-oriented programming.
In short, polymorphism in C# means we can treat instances of a derived class as though they are instances of their base class. For example, let's define a base class and two derived classes, as well as a method which takes an instance of the base class as a parameter.
public class Animal //Base class
{
public string SpeciesName { get; set; }
public string CommonName { get; set; }
}
public class Elephant : Animal { /*...*/ } //Derived class
public class Porcupine : Animal //Derived class
{
public string OrderType { get; set; } //Old world or New world
}
public static class StaticMethods
{
public static string GetAnimalDetails(Animal animal)
{
return animal.SpeciesName + "(AKA " + animal.CommonName + ")";
}
}
If we instantiate an Elephant
object and a Porcupine
object, we can pass both of them to the GetAnimalDetails
method and the method will treat each as though they are instances of the Animal
class:
var porcupine = new Porcupine();
porcupine.OrderType = "New World";
var details = GetAnimalDetails(porcupine);
However, the method GetAnimalDetails()
will not be able to access the property OrderType
, because that property is defined in the derived class Porcupine
and not in the base class Animal
.
public void GetAnimalDetails(Animal animal)
{
string type = animal.OrderType; //COMPILATION ERROR
return;
}
Virtual Methods
We can work with polymorphism by implementing a C# feature that we saw in the Inheritance examples: virtual methods. These are methods defined on the base class that allow for the derived classes to implement some additional behavior, and optionally run the behavior defined in the base class.
We do this using the virtual
keyword in the base class and the override
keyword in the derived class:
public class Animal
{
public virtual void Eat(string meal)
{
Console.WriteLine("Digesting " + meal);
}
}
public class Fox : Animal
{
public override void Eat(string meal)
{
Console.WriteLine("Chewing " + meal);
base.Eat(meal);
}
}
public class Anteater : Animal //They have no teeth!
{
public override void Eat(string meal)
{
Console.WriteLine("Swallowing " + meal);
//NOTE: no call to base class here!
}
}
var myFox = new Fox();
myFox.Eat("rabbit"); //Output: "Chewing rabbit"
// "Digesting rabbit"
var myAnteater = new Anteater();
myAnteater.Eat("ants"); //Output: "Swallowing ants"
Virtual Properties
In addition to virtual methods, we can also have virtual properties on the base class that can be overridden by the derived classes.
Virtual methods and virtual properties allow us developers to extend the functionality of the base class without needing to use that base functionality.
Stopping Virtual Inheritance
It is possible to prevent derived classes from inheriting virtual
methods. Let's see a new base class A
and a derived class B
:
public class A
{
public virtual void GetDetails()
{
Console.WriteLine("A.GetDetails invoked!");
}
}
public class B : A
{
public override void GetDetails()
{
Console.WriteLine("B.GetDetails invoked!");
base.GetDetails();
}
}
A derived class can stop virtual inheritance by declaring a member, method, or property as sealed
(note that class C
inherits from class A
):
public class C : A
{
public sealed override void GetDetails()
{
Console.WriteLine("C.GetDetails invoked!");
base.GetDetails();
}
}
Which means that further-derived classes can no longer inherit the sealed
method, property, or member:
public class D : C
{
//We cannot override GetDetails()!
}
However, we can also implement a new version of the member, one whose definition is restricted to the class it was defined in, using the new
keyword.
public class E : C
{
public new void GetDetails()
{
Console.WriteLine("E.GetDetails invoked!");
}
}
var myClassE = new E();
myClassE.GetDetails(); //Calls the implementation in Class E
var myClassC = new C();
myClassC.GetDetails(); //Calls the implementation in Class C, then Class A
Fun with Polymorphism
Polymorphism becomes particularly useful in, say, a method that can accept many types but does something different with each of them.
public class Animal { /*...*/ } //Base class
public class Elephant : Animal //Derived class
{
public string ElephantType { get; set; } //African, Asian, Other?
}
public class Dolphin : Animal //Derived class
{
public string DolphinType { get; set; } //Bottlenose, common, other?
}
public static class FunWithPolymorphism
{
public static void OutputType(Animal animal)
{
if (animal.GetType() == typeof(Dolphin)) //GetType defined
//on System.Object
{
var dolphin = (Dolphin)animal;
Console.WriteLine(dolphin.DolphinType);
}
if (animal.GetType() == typeof(Elephant))
{
var elephant = (Elephant)animal;
Console.WriteLine(elephant.ElephantType);
}
}
}
public static void Main(string[] args)
{
var dolphin = new Dolphin()
{
DolphinType = "Bottlenose"
};
var elephant = new Elephant()
{
ElephantType = "Asian"
};
FunWithPolymorphism.OutputType(elephant);
FunWithPolymorphism.OutputType(dolphin);
Console.ReadLine();
}
Glossary
- Inheritance - a property of object-oriented programming languages that allows for classes to adopt and use members, properties, methods, etc. defined in another class.
- Base class - a C# class which is inherited by another class.
- Derived class - a C# class which is inheriting from another class.
- Multiple inheritance - where a single class inherits from multiple other classes. This is not permitted in C#; attempting to do this will cause a build error.
- Polymorphism - a property of object-oriented programming languages that allows for a derived class to be treated as though it is an instance of its base class.
- Virtual methods - Methods in a base class that can be overridden by that base's derived classes.
- Virtual properties - Properties in a base class that can be overridden by that base's derived classes.
New Keywords
base
- Used to call constructors in base classes.override
- Implements new or additional behaviorvirtual
- Specifies that a member, property, method, etc. can be overridden.sealed
- Specifies that a class or member cannot be inherited.
Summary
Inheritance is the ability for classes to use members from other classes. The class that implements the original behavior is called a base class, and the class that inherits from a base is called a derived class.
Polymorphism allows for instances of derived classes to be treated as though they are instances of their base class. We can allow for implementation in a base class to be extended using virtual
methods and properties, which can optionally call the base implementation in addition to their implementation.
Got questions about inheritance or polymorphism? I don't blame you! These are complicated subjects that are difficult to describe thoroughly in a blog post. If you need help understanding these ideas, please ask questions in the comments below!
In the next part of this series, as mentioned earlier, we see and discuss other ways we can have C# classes implement behavior, properties, and methods. Specifically, we talk about interfaces, which define a common set of properties and methods for multiple classes, and abstract classes, which don't implement behavior but allow for derived classes to implement their own. Check it out!
Happy Coding!