Skip to Content
Course content

28: Sealed Classes and Members

Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you're first learning about object-oriented programming, you're told that inheritance is one of the "big wins." You're encouraged to make your code extensible. Naturally, this leads most developers to a mindset where they leave every class open for inheritance, just in case someone—including their future self—needs to extend it later. I'll admit, I spent the first few years of my career doing exactly that. I thought sealing a class was like putting a "No Trespassing" sign on my code; it felt restrictive and contrary to the spirit of flexibility.

The "Leave it Open" Mentality

Imagine we're building a payment processing system. We have a base PaymentProcessor and a specific implementation for credit cards. In a naive approach, you'd write it like this:

public class PaymentProcessor 
{
    public virtual void ProcessPayment(decimal amount) 
    {
        Console.WriteLine($"Processing generic payment of {amount:C}");
    }
}

public class CreditCardProcessor : PaymentProcessor
{
    public override void ProcessPayment(decimal amount)
    {
        // Complex logic for communicating with a bank gateway
        Console.WriteLine($"Charging {amount:C} to Credit Card...");
    }
}

At first glance, this is perfectly fine. But here is the problem: by leaving CreditCardProcessor open, you're telling every other developer on your team that it's safe to inherit from it. Now, imagine a junior dev wants to implement a "Loyalty Credit Card" and decides to inherit from CreditCardProcessor to override ProcessPayment. They might accidentally skip a critical security check or a logging step that you baked into the original credit card logic because they didn't fully understand the internals of the gateway communication.

When Inheritance Becomes a Liability

This is what we call the "Fragile Base Class" problem. When you allow deep inheritance chains, a small change in a base class can ripple down and break things in derived classes in ways that are incredibly hard to debug. More importantly, CreditCardProcessor is a "leaf" in your business logic. There is no logical reason for it to be a parent to another class. It represents a specific, final implementation of a process.

By not sealing this class, you've created an implicit contract that you are willing to support any future modifications to the internal behavior of credit card processing via inheritance. In a professional codebase, that's a liability you don't want unless you've intentionally designed the class to be a framework component.

Closing the Door with Sealed

The better way is to be intentional. If a class is designed to be a final implementation, mark it as sealed. This tells the compiler—and your colleagues—that this class is the end of the line.

public sealed class CreditCardProcessor : PaymentProcessor
{
    public override void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Charging {amount:C} to Credit Card...");
    }
}

Now, if someone tries to inherit from CreditCardProcessor, the code won't even compile. You've moved the error from "runtime bug in production" to "compile-time error in the IDE." I always prefer the latter. It forces the other developer to think: "Why am I inheriting from this? Should I be using composition instead?"

You can also seal specific members. This is useful when you have a hierarchy three levels deep. You might want the second level to be extendable, but you want to stop a specific method from being overridden any further down the chain. You do this by combining sealed with override:

public class PaymentProcessor { public virtual void Validate() { } }
public class DigitalPayment : PaymentProcessor 
{ 
    // I'm overriding Validate, but I'm sealing it so 
    // classes inheriting from DigitalPayment can't change it.
    public sealed override void Validate() 
    { 
        Console.WriteLine("Performing digital signature check..."); 
    } 
}

The Performance Bonus

Beyond the architectural safety, there's a technical win here that often goes unmentioned. When the JIT (Just-In-Time) compiler sees a call to a virtual method, it has to look up the actual type of the object at runtime to find the correct method to call—this is called a virtual table lookup. However, if the class is sealed, the compiler knows there are no derived classes. It can often "devirtualize" the call, turning it into a direct call to the method. In a tight loop processing thousands of payments, this can provide a measurable performance boost. It's not usually the primary reason to seal a class, but it's a nice cherry on top.




📋 Practical Task

Exercise: Hardening the SecureVault Implementation

You are reviewing a security module for a financial application. The current implementation allows for dangerous inheritance that could lead to security bypasses. Your task is to refactor the code to prevent unauthorized extensions.

Requirements:

  • You have a base class Vault with a virtual method Open().
  • You have a derived class BiometricVault that overrides Open() to implement fingerprint scanning.
  • The BiometricVault is a final implementation and should not be inherited from.
  • There is a second derived class TimedVault that overrides Open(). Inside TimedVault, there is a method called LogAccess() that is an override from a middle-tier class; this specific logging method must be sealed so that no further subclasses can disable the logging.

Your Task: Rewrite the provided classes below, applying the sealed keyword to the appropriate classes and members to ensure the architectural integrity of the vault system.

public class Vault 
{
    public virtual void Open() => Console.WriteLine("Vault opening...");
}

public class BiometricVault : Vault 
{
    public override void Open() => Console.WriteLine("Scanning fingerprint...");
}

public class BaseTimedVault : Vault 
{
    public override void Open() => Console.WriteLine("Waiting for timer...");
    public virtual void LogAccess() => Console.WriteLine("Access logged.");
}

public class TimedVault : BaseTimedVault 
{
    public override void LogAccess() => Console.WriteLine("Securely logging access to encrypted file...");
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.