C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
28: Sealed Classes and Members
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
Vaultwith a virtual methodOpen(). - You have a derived class
BiometricVaultthat overridesOpen()to implement fingerprint scanning. - The
BiometricVaultis a final implementation and should not be inherited from. - There is a second derived class
TimedVaultthat overridesOpen(). InsideTimedVault, there is a method calledLogAccess()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...");
}There are no comments for now.