DEV Community

Sathish
Sathish

Posted on • Originally published at sathishsaravanan.com

Liskov Substitution Principle in C#: Subclass Without Surprises

Inheritance Without Betrayal

Inheritance is powerful—but with great power comes… well, you know the rest.

Ever swapped in a subclass and everything broke quietly? That’s a violation of the Liskov Substitution Principle (LSP)—the "L" in SOLID.

Objects of a superclass should be replaceable with objects of its subclasses without breaking the application. — Barbara Liskov

You miss 100% of the principles you ignore. — Me, channeling Michael Scott” — Sathish Kumar Saravanan

Let’s unpack what that really means, using some straightforward C# examples and a healthy dose of practicality.

What’s the Big Idea?

When you inherit from a base class, you're making a promise:

You can use me anywhere you'd use the base class, and things will still work as expected.

The moment a subclass starts throwing exceptions, skipping behavior, or changing meaning—you’ve broken that promise. Your design is fragile, even if the compiler says it’s fine.

Let’s look at a real-world example.

A Violation in Disguise

Imagine you’re building a system that handles documents:

public class Document
{
    public virtual void Print()
    {
        Console.WriteLine("Printing document...");
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let’s say we want to support digital-only documents:

public class DigitalDocument : Document
{
    public override void Print()
    {
        throw new NotSupportedException("Digital documents can't be printed.");
    }
}
Enter fullscreen mode Exit fullscreen mode

Uh oh.

Sure, it compiles. But if someone uses DigitalDocument in a place expecting a Document, things break.

That’s exactly what LSP warns us about.

✅ Refactor to Respect LSP

Instead of forcing everything into a single hierarchy, let’s define capabilities more explicitly.

public interface IPrintable
{
    void Print();
}

public class PaperDocument : IPrintable
{
    public void Print()
    {
        Console.WriteLine("Printing paper document...");
    }
}

public class DigitalDocument
{
    public void Open()
    {
        Console.WriteLine("Opening digital document...");
    }
}
Enter fullscreen mode Exit fullscreen mode

Now DigitalDocument never pretends to be printable. And PaperDocument cleanly implements what it can actually do.

No surprise exceptions. No broken expectations. Everyone’s behaving themselves.

Why This Matters in the Real World

Violating LSP leads to:

  • 🚨 Unexpected runtime errors
  • 🧪 Tests that pass for base classes but fail for subclasses
  • 🧱 Code that becomes harder to extend safely

Respecting LSP keeps your inheritance trees clean and your codebase honest. Subclasses don’t have to do everything, but they shouldn’t pretend to either.

A Quick LSP Check

Ask yourself:

  • Can I pass this subclass wherever the base class is expected… and still trust the behavior?
  • Does this subclass silently remove or alter functionality?
  • Would a user of the base class be surprised by this subclass?

If the answer’s “uh… maybe,” it’s time to refactor.

Final Thoughts

The Liskov Substitution Principle is really about predictability.

Inheritance is meant to give you power, not headaches. When you design subclasses that follow LSP, your systems become safer, more understandable, and easier to refactor.

Don’t build traps disguised as child classes. Build confidence. Build clarity.

If your subclass breaks the rules of the base class, it’s not inheritance—it’s sabotage.

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay