top of page

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

Writer's pictureAhmed Tarek

SOLID: Liskov Substitution Principle Explained In .NET C#

Updated: Apr 17

Understand the Liskov Substitution Principle of the SO(L)ID principles in .NET C#


The Liskov Substitution Principle Explained in .NET C#. Really understand the Liskov Substitution Principle of the SOLID principles in DotNet (.NET) CSharp (C#)
Photo by Brett Jordan on Unsplash

Almost all developers working with Object Oriented Programming (OOP) languages know about the SOLID principles.


SOLID Principles

  • Single Responsibility Principle

  • Open-closed Principle

  • Liskov Substitution Principle

  • Interface Segregation Principle

  • Dependency Inversion Principle


For this article, we are going to explain the L of the SOLID, Liskov Substitution Principle.


 

What is Liskov Substitution Principle


The Liskov Substitution Principle was introduced by Barbara Liskov in her conference keynote Data abstraction in 1987. The main title was Data abstraction and hierarchy in a computer program.


A few years later, and in cooperation with Jeanette Wing, they published a paper where they defined the principle as:

Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T.

I know that this definition seems to be too much academic than practical. And that’s why we are having this article.


 


 

What is Liskov Substitution Principle really about


It is about how you structure your solution hierarchy using abstractions while keeping the logic intact and robust.


If I try to translate the the academic definition, I would say that if an object B inherits/extends object A, it is supposed that any logic expecting A should perform correctly if B is passed to it.


However, this is just the direct translation, but, if you ask me, I would tell you that I like to give the Liskov Substitution Principle another name; The Hidden Intentions Principle.


If you are already working with an Object Oriented Programming (OOP) language, you would know that most compilers -if not all- would do static checking to make sure that if class B inherits from class A, all members in A would be found in B. Therefore, this is not actually the point.


The point is that you need to make sure that the code of B is not hiding any intentions that could break the logic. This is something that the compiler could not detect, you need to do it yourself.


 

The Liskov Substitution Principle Explained in .NET C#. Really understand the Liskov Substitution Principle of the SOLID principles in DotNet (.NET) CSharp (C#)
Photo by Lysander Yuen on Unsplash

The Example


If you search the internet for an example to understand the Liskov Substitution Principle, I am sure you would come across the famous Rectangle and Square example. And actually, I find it a good example.


In the world of mathematics, a Square is a Rectangle but under some conditions. So, if you are going to model the Square and the Rectangle in an OOP language, you might say that the Square should inherit/extend the Rectangle.


However, in the real world, are you sure this is the right decision?


 


 

The Liskov Substitution Principle Explained in .NET C#. Really understand the Liskov Substitution Principle of the SOLID principles in DotNet (.NET) CSharp (C#)
Photo by Arnold Francisca on Unsplash

Time for Code


Let’s start implementing the Rectangle and Square and see how it goes.



Rectangle




Square



Here, we are having some hidden intentions that the external user doesn’t know about. For a Square, when you set the Width to a certain value, you also set the Height to the same value. The same, when you set the Height to a certain value, you also set the Width to the same value.


Would this cause any problem? Let’s see…



Program



Here we can see the problem. Let’s analyze what would happen.

When the line StretchRectangle(new Rectangle(2, 5)); is executed, this would happen:

  • new Rectangle(2, 5) would create a Rectangle with Width = 2 and Height = 5.

  • rectangle.Width *= 2 would set the Width to 4.

  • rectangle.Height *= 2 would set the Height to 10.

  • Then the final area would be 40.


This is expected.


However, when the line StretchRectangle(new Square(2, 5)); is executed, this would happen:

  • new Square(2, 5) would create a Square.

  • In the constructor, when setting its Width to 2, its Height would also be set to 2.

  • Also, in the constructor, when setting its Height to 5, its Width would also be set to 5.

  • So, up till now Width = 5 and Height = 5.

  • rectangle.Width *= 2 would set the Width to 10, and also the Height to 10.

  • rectangle.Height *= 2 would set the Height to 20, and also the Width to 20.

  • Then the final area would be 400.


This is not expected at all.


 

The Liskov Substitution Principle Explained in .NET C#. Really understand the Liskov Substitution Principle of the SOLID principles in DotNet (.NET) CSharp (C#)
Photo by Aaron Burden on Unsplash

Final Thoughts


As you can see, the problem we faced here is not caused by a wrong syntax, it is caused by some hidden intentions.


Therefore, abstractions and inheritance are good but you need to think about your design of abstractions and avoid having any hidden intentions that might break your logic.



Recent Posts

See All

1 Comment

Rated 0 out of 5 stars.
No ratings yet

Add a rating
Guest
Feb 26
Rated 5 out of 5 stars.

A very nice example of why you need immutability by the end of a ctor.

Replace setters with init;

Add a validation in the square ctor;

Adding business to setter is what causes the problem. Two definitions having business for each other.

Ctor is responsible for constructing a proper object. Simple source of truth. When a ctor finishes, always make sure that the object can't be changed from that moment on. In the ctor, a simple if check to see width-height is equal and then set both to first value.

I'm also against to idea of "height" and "width" altogether. Bad design is sometimes caused by misnaming.

Side is better. More agnostic. A simple vector with a length. Constructing proper…

Edited
Like

Subscribe to get best practices, tutorials, and many other cool things directly to your email inbox

bottom of page
Mastodon Mastodon