A small, hands-on tutorial that explains inheritance, method overriding, and virtual methods in C#. It was originally written and delivered as an internal training session (with the accompanying PowerPoint, included in this repository) around 2009, and has been modernised to build and run on current .NET.
Each concept has its own tiny console project so you can read the code, run it, and see exactly what gets printed — and, just as importantly, why.
The original slide deck is included as
Virtual Methods and Inheritance.pptx(and a.ppsslideshow version). This README reproduces its content so everything is readable directly on GitHub.
By the end of this tutorial you should understand:
- Simple inheritance — how a class inherits members from a base class.
- How to override inherited methods — replacing a base method in a derived class.
- How to control overriding using
virtualmethods — and what happens when a method isn't markedvirtual.
You need the .NET SDK (8.0 or later).
# Build everything
dotnet build Inheritance.sln
# Run any single example (replace with the project name)
dotnet run --project OverrideThe projects are interactive console apps — each ends with Console.ReadKey() and waits
for a key press before closing, so run them from a terminal.
Originally these were Visual Studio 2008 / .NET Framework 3.5 projects. They have been converted to SDK-style projects targeting
net8.0. The lesson code itself is unchanged — only the project plumbing was modernised so the examples run on any OS.
| Project | Concept | Key point |
|---|---|---|
Inheritance |
Simple inheritance | A derived class gains the base class's members. |
OverrideWithoutVirtual |
Why virtual matters |
Trying to override a non-virtual method fails to compile. |
Override |
Overriding a virtual method |
The derived method runs even through a base-class reference (polymorphism). |
Base |
The base keyword |
An override calls the base implementation with base.SayHello(). |
MultipleDerivations |
Many classes, one base | Several classes derive from the same base and each overrides differently. |
VirtualHierarchy |
Multi-level hierarchies | Overrides flow through several layers of inheritance. |
Abstract |
Abstract classes | A base may declare a method with no body; it cannot be instantiated. |
Classes may inherit members from another class.
Terminology
- A class inheriting members is derived from a base class.
- The class providing members is the base class of the derived class.
- A derived class may be referred to in terms of its base class.
- A class may inherit from one base class.
- The base class may itself be a derived class.
- Many classes may derive from the same base class.
public class BaseClass
{
public void SayHello()
{
Console.WriteLine("Hello World from BaseClass.");
}
}
public class DerivedClass : BaseClass
{
public void SayBye()
{
Console.WriteLine("Goodbye World from DerivedClass.");
}
}DerivedClass now provides two methods: the inherited SayHello() and its own
SayBye().
BaseClass baseClass = new BaseClass();
baseClass.SayHello();
// -> Hello World from BaseClass.
DerivedClass derivedClass = new DerivedClass();
derivedClass.SayHello(); // inherited
derivedClass.SayBye(); // its own
// -> Hello World from BaseClass.
// -> Goodbye World from DerivedClass.
BaseClass derivedClassAsBaseClass = new DerivedClass();
derivedClassAsBaseClass.SayHello();
// -> Hello World from BaseClass.(See the Inheritance project.)
Inherited methods may be replaced (overridden).
- Use the
overridekeyword in C#. - When you refer to a derived object through its base class, the overridden
method in the derived class is the one that runs.
(This is the opposite of the
newkeyword in a method signature, which hides rather than overrides.) - Only
virtualorabstractmethods can be overridden. - A method can be re-overridden in each class that inherits it.
public class BaseClass
{
public void SayHello() // not virtual!
{
Console.WriteLine("Hello World from BaseClass.");
}
}
public class DerivedClass : BaseClass
{
public override void SayHello() // <-- compiler error
{
Console.WriteLine("Hello World from DerivedClass.");
}
}This does not compile:
'DerivedClass.SayHello()': cannot override inherited member
'BaseClass.SayHello()' because it is not marked virtual, abstract, or override
(The OverrideWithoutVirtual project demonstrates this.
The illegal override is kept in the source as a clearly-marked comment so the project
still builds — uncomment it to reproduce the compiler error for yourself.)
virtualidentifies methods which may be overridden.
- Indicated with the
virtualkeyword. - It's a safety feature that not every object-oriented language has (Java, for example,
makes methods overridable by default).
- Advantage: makes it clear where overriding is intended and prevents inappropriate overrides.
- Drawback: you have to decide, for every method, whether it should allow overrides.
- A method is declared
virtualonce, before its first override. Overridden definitions can themselves be overridden further down the hierarchy.
public class BaseClass
{
public virtual void SayHello()
{
Console.WriteLine("Hello World from BaseClass.");
}
}
public class DerivedClass : BaseClass
{
public override void SayHello()
{
Console.WriteLine("Hello World from DerivedClass.");
}
public void SayBye()
{
Console.WriteLine("Goodbye World from DerivedClass.");
}
}Now compare the output with the plain-inheritance example. The crucial line is the last one — accessing the derived object through a base-class variable still calls the derived method:
BaseClass baseClass = new BaseClass();
baseClass.SayHello();
// -> Hello World from BaseClass.
DerivedClass derivedClass = new DerivedClass();
derivedClass.SayHello();
derivedClass.SayBye();
// -> Hello World from DerivedClass.
// -> Goodbye World from DerivedClass.
BaseClass derivedClassAsBaseClass = new DerivedClass();
derivedClassAsBaseClass.SayHello();
// -> Hello World from DerivedClass. <-- the override wins(See the Override project.)
An override can still call the implementation it replaced, using base:
public override void SayHello()
{
base.SayHello(); // run BaseClass.SayHello() first
Console.WriteLine("- And Hello World from DerivedClass.");
}(See the Base project.)
public class BaseClass
{
public virtual void SayHello()
{
Console.WriteLine("Hello World from BaseClass.");
}
}
public class BaseDerivedClass : BaseClass
{
public virtual void SayBye()
{
Console.WriteLine("Goodbye World from BaseDerivedClass.");
}
}
public class DerivedClassA : BaseDerivedClass
{
public override void SayBye()
{
Console.WriteLine("Goodbye World from DerivedClassA.");
}
public override void SayHello()
{
Console.WriteLine("Hello World from DerivedClassA.");
}
}
public class DerivedClassB : BaseClass
{
public void SayBye()
{
Console.WriteLine("Goodbye World from DerivedClassB.");
}
}What would be printed to the console for each of the following?
DerivedClassA myClass = new DerivedClassA();
myClass.SayHello();
DerivedClassB myClass = new DerivedClassB();
myClass.SayHello();
BaseClass myClass = new DerivedClassA();
myClass.SayHello();
BaseClass myClass = new DerivedClassA();
myClass.SayBye();Hello World from DerivedClassA.—DerivedClassAoverridesSayHello().Hello World from BaseClass.—DerivedClassBdoes not overrideSayHello(), so the inherited version runs.Hello World from DerivedClassA.— the override is called even through aBaseClassreference, becauseSayHello()isvirtual.- Nothing —
BaseClassdoes not expose aSayBye()method, so this last line would not even compile. (SayBye()lives further down the hierarchy.)
(See the VirtualHierarchy project, which exercises every one of
these cases, and MultipleDerivations for several siblings
sharing one base class.)
A base class can declare a method with no implementation at all using abstract.
An abstract class cannot be instantiated directly — only its concrete derived classes
can be created.
public abstract class BaseClass
{
public abstract void SayHello(); // no body
}
public class DerivedClass : BaseClass
{
public override void SayHello()
{
Console.WriteLine("Hello World from DerivedClass.");
}
}// BaseClass baseClass = new BaseClass(); // compiler error: cannot create an
// // instance of the abstract type
BaseClass derivedClassAsBaseClass = new DerivedClass();
derivedClassAsBaseClass.SayHello();
// -> Hello World from DerivedClass.(See the Abstract project.)
Describe how virtual methods and inheritance could be used to derive a
Cubeclass from aSquareclass, where both provide aGetSurfaceArea()method.Tip: the surface area of a cube is six times the area of one of its square faces.
- Referring to a base class using the
basekeyword. (shown in theBaseproject) - Method hiding using the
newkeyword (different from overriding). - How to override properties as well as methods.
- Abstract classes and members. (shown in the
Abstractproject)
This material dates from around 2009 and originally targeted C# 3.0 on .NET Framework 3.5
in Visual Studio 2008. The concepts — virtual, override, abstract, and the base
keyword — are unchanged in modern C#, which is why the original lesson code compiles and
runs as-is on .NET 8. Newer language features (such as top-level statements or
file-scoped namespaces) are intentionally avoided here so the focus stays on inheritance.