Skip to content

C# - Inheritance

Inheritance is a fundamental pillar of object-oriented programming (OOP). It allows a class to inherit fields and methods from another class, known as the base (or parent) class. The inheriting class is called the derived (or child) class. This mechanism promotes code reusability and helps model real-world ‘is-a’ relationships, leading to more organized and maintainable applications.

By using inheritance, you can create a general base class that defines common functionality and then create more specific derived classes that add or modify that functionality. For example, a Car is a Vehicle, and a Bicycle is a Vehicle. Vehicle would be the base class, while Car and Bicycle would be derived classes.

In C#, a class can inherit from only one base class (single inheritance), but it can implement multiple interfaces. This design prevents the complexity and ambiguity issues associated with multiple class inheritance, like the ‘Diamond Problem’.

The syntax for creating a derived class is simple. You specify the derived class name, followed by a colon (:) and the name of the base class.

// Base class
public class BaseClass
{
// Members of the base class
}
// Derived class
public class DerivedClass : BaseClass
{
// Members of the derived class
}

Let’s modernize the classic Shape and Rectangle example using modern C# properties and constructors. Properties provide a more flexible and safer way to expose data than public fields or simple getter/setter methods.

using System;
namespace InheritanceExample
{
// Base class with properties
public class Shape
{
// Auto-implemented properties for width and height
public double Width { get; set; }
public double Height { get; set; }
public void DisplayDimensions()
{
Console.WriteLine($"Width: {Width}, Height: {Height}");
}
}
// Derived class
public class Rectangle : Shape
{
// Method specific to Rectangle
public double GetArea() => Width * Height;
}
public class Program
{
public static void Main(string[] args)
{
var rect = new Rectangle();
// Set properties inherited from Shape
rect.Width = 5.5;
rect.Height = 3.0;
rect.DisplayDimensions();
// Call method from the derived class
Console.WriteLine($"Area: {rect.GetArea()}");
}
}
}

When the above code is compiled and executed, it produces the following result:

Width: 5.5, Height: 3.0
Area: 16.5

A derived class constructor must call a constructor of its base class. If you don’t explicitly call a base constructor, the compiler will try to call the default (parameterless) constructor of the base class. To call a specific base class constructor, you use the base keyword in the derived class’s constructor declaration.

Here’s an example showing how a Tabletop class can initialize its Rectangle base class using a constructor.

using System;
namespace BaseInitialization
{
public class Rectangle
{
public double Length { get; }
public double Width { get; }
// Base class constructor
public Rectangle(double length, double width)
{
Length = length;
Width = width;
}
public double GetArea() => Length * Width;
public virtual void Display()
{
Console.WriteLine($"Length: {Length}");
Console.WriteLine($"Width: {Width}");
Console.WriteLine($"Area: {GetArea()}");
}
}
public class Tabletop : Rectangle
{
public decimal CostPerSquareUnit { get; set; }
// Derived class constructor calling the base constructor
public Tabletop(double length, double width, decimal costPerSquareUnit) : base(length, width)
{
CostPerSquareUnit = costPerSquareUnit;
}
public decimal GetTotalCost() => (decimal)GetArea() * CostPerSquareUnit;
// Override the Display method to add more info
public override void Display()
{
base.Display(); // Call the base class's Display method
Console.WriteLine($"Total Cost: {GetTotalCost():C}"); // :C formats as currency
}
}
public class Program
{
public static void Main(string[] args)
{
var table = new Tabletop(4.5, 7.5, 70m); // 'm' suffix for decimal
table.Display();
}
}
}

When the above code is compiled and executed, it produces the following result:

Length: 4.5
Width: 7.5
Area: 33.75
Total Cost: $2,362.50

A derived class can provide its own implementation of a method that it inherited from a base class. To override a method, the base class method must be marked with the virtual, abstract, or override keyword. The derived class method must then use the override keyword.

This example demonstrates polymorphism, where an Animal variable can hold a Dog object, and calling MakeSound() executes the overridden method from the Dog class.

using System;
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Some generic animal sound");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Woof! Woof!");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("Meow");
}
}
public class Program
{
public static void Main()
{
Animal myAnimal = new Animal();
Animal myDog = new Dog(); // A Dog is an Animal
Animal myCat = new Cat(); // A Cat is an Animal
myAnimal.MakeSound();
myDog.MakeSound();
myCat.MakeSound();
}
}

When executed, this program outputs:

Some generic animal sound
Woof! Woof!
Meow

You can prevent a class from being used as a base class by marking it with the sealed keyword. This is useful for classes that are not designed to be extended. For example, the string class in .NET is sealed.

public sealed class FinalClass
{
public void ShowMessage()
{
Console.WriteLine("This class cannot be inherited.");
}
}
// The following line would cause a compilation error:
// public class AttemptToDerive : FinalClass { } // Error: 'AttemptToDerive': cannot derive from sealed type 'FinalClass'

While C# does not support inheriting from multiple classes, a class can implement multiple interfaces. An interface defines a contract—a set of public methods and properties—that the implementing class must provide. This allows you to achieve polymorphism and code reuse in a safe and flexible way.

Best Practice: Prefer implementing interfaces over class inheritance when you only need to define a contract of capabilities (e.g., ISerializable, IComparable).

Here, a Car class inherits from Vehicle and also implements the IDrivable and IChargeable interfaces, representing a modern electric car.

using System;
// Interface for things that can be driven
public interface IDrivable
{
void Start();
void Stop();
void Drive();
}
// Interface for things that can be charged
public interface IChargeable
{
int BatteryLevel { get; }
void Charge();
}
// Base class
public abstract class Vehicle
{
public string Make { get; set; }
public string Model { get; set; }
}
// Derived class implementing multiple interfaces
public class ElectricCar : Vehicle, IDrivable, IChargeable
{
public int BatteryLevel { get; private set; } = 50;
public void Start()
{
Console.WriteLine("Electric car started silently.");
}
public void Stop()
{
Console.WriteLine("Regenerative braking engaged. Car stopped.");
}
public void Drive()
{
if (BatteryLevel > 0)
{
Console.WriteLine("Driving the car, whirring sound.");
BatteryLevel -= 5;
}
else
{
Console.WriteLine("Battery is empty!");
}
}
public void Charge()
{
Console.WriteLine("Charging...");
BatteryLevel = 100;
Console.WriteLine("Battery fully charged.");
}
}
public class Program
{
public static void Main()
{
var myTesla = new ElectricCar { Make = "Tesla", Model = "Model Y" };
myTesla.Start();
myTesla.Drive();
Console.WriteLine($"Current battery: {myTesla.BatteryLevel}%");
myTesla.Charge();
Console.WriteLine($"Current battery: {myTesla.BatteryLevel}%");
myTesla.Stop();
}
}

When the above code is compiled and executed, it produces a result similar to this:

Electric car started silently.
Driving the car, whirring sound.
Current battery: 45%
Charging...
Battery fully charged.
Current battery: 100%
Regenerative braking engaged. Car stopped.