C# Liskov Substitution Principle

Summary: in this tutorial, you’ll learn about the C# Liskov substitution principle and how to apply it to build more scalable and maintainable software applications.

Introduction to the C# Liskov Substitution Principle

The Liskove substitution principle (LSP) is the third principle in the SOLID principles of object-oriented design:

Note that the Liskov substitution principle is named after Barbara Liskov, who first formulated it in 1988.

The Liskov substitution principle states that if a method uses a base class, then it should be able to use any of its derived classes without the need of having the information about the derived class.

In other words, the derived classes should be substitutable for their base class without causing errors or side effects. This means that the behavior of the derived class should not contradict the behavior of the base class.

Let’s take an example to understand the Liskov substitution principle in C#:

namespace LSP;

public abstract class Vehicle
{
    public abstract void Drive();
}

public class Car : Vehicle
{
    public override void Drive() => Console.WriteLine("Drive a car");
}

public class Truck : Vehicle
{
    public override void Drive() => Console.WriteLine("Drive a truck");
}

public class Program
{
    public static void TestDrive(Vehicle vehicle)
    {
        vehicle.Drive();
    }
    public static void Main(string[] args)
    {
        var car = new Car();
        TestDrive(car);

        var truck = new Truck();
        TestDrive(truck);
    }
}Code language: C# (cs)

How it works.

First, define an abstract class Vehicle that has a Drive() abstract method:

public abstract class Vehicle
{
    public abstract void Drive();
}Code language: C# (cs)

Second, define the Car and Truck classes that inherit from the Vehicle class. The Drive() method in each class displays a message in the console:

public class Car : Vehicle
{
    public override void Drive() => Console.WriteLine("Drive a car");
}

public class Truck : Vehicle
{
    public override void Drive() => Console.WriteLine("Drive a truck");
}Code language: C# (cs)

Third, define the TestDrive() static method in the Program class that takes a Vehicle as the parameter. The TestDrive() static method calls the Drive() method of the vehicle object:

public class Program
{
    public static void TestDrive(Vehicle vehicle)
    {
        vehicle.Drive();
    }
    
   // ...
}Code language: C# (cs)

Finally, pass an instance of a Car class to the TestDrive() static method, which calls the Drive() method of the Car object. Similarly, pass an instance of the Truck class to the TestDrive() static method, which calls the Drive() method of the truck object:

public class Program
{
    // ...

    public static void Main(string[] args)
    {
        var car = new Car();
        TestDrive(car);

        var truck = new Truck();
        TestDrive(truck);
        
    }
}Code language: C# (cs)

This is an example of the Liskov substitution principle in action. We are able to use the Car and Truck objects in the TestDrive() method without causing any errors.

In other words, we can substitute the derived classes (Car & Truck) for their base class (Vehicle) in the TestDrive() method.

Now, let’s violate the Liskov substitution principle by introducing a new derived class called Aircraft. Since you can only fly an aircraft, not drive it, the Drive() method in the Aircraft class raises an exception NotImplementedException:

public class Aircraft : Vehicle
{
    public override void Drive() => throw new NotImplementedException();
}Code language: C# (cs)

When we pass an instance of the Aircraft object to the TestDrive() method, it creates a side effect (or an exception)

Because we cannot substitute the Aircraft for its base class Vehicle in the TestDrive() method, it violates the Liskov substitution principle.

The following shows the whole program that violates the Liskov substitution principle:

namespace LSP;

public abstract class Vehicle
{
    public abstract void Drive();
}

public class Car : Vehicle
{
    public override void Drive() => Console.WriteLine("Drive a car");
}

public class Truck : Vehicle
{
    public override void Drive() => Console.WriteLine("Drive a truck");
}

public class Aircraft : Vehicle
{
    public override void Drive() => throw new NotImplementedException();
}

public class Program
{
    public static void TestDrive(Vehicle vehicle)
    {
        vehicle.Drive();
    }
    public static void Main(string[] args)
    {
        var car = new Car();
        TestDrive(car);

        var truck = new Truck();
        TestDrive(truck);

        
        var aircraft = new Aircraft();
        TestDrive(aircraft); // side effect
    }
}Code language: C# (cs)

Summary

  • The Liskov substitution principle states that derived classes should be substitutable for their base class without causing any errors.
Was this tutorial helpful ?