C# Sealed Classes
Introduction
In C# object-oriented programming, the sealed
keyword plays a significant role in controlling inheritance. When applied to a class, it prevents other classes from inheriting from it. This feature might seem restrictive at first, but it serves important purposes in application design and optimization.
In this tutorial, we'll explore sealed classes in C#, understand their purpose, learn how to use them, and see real-world examples where they make sense.
What Are Sealed Classes?
A sealed class is a class that cannot be used as a base class. Once a class is marked as sealed
, no other class can inherit from it. This is essentially a way to prevent further derivation of a class.
Here's the basic syntax for creating a sealed class:
sealed class SealedClass
{
// Class members
}
Why Use Sealed Classes?
There are several reasons why you might want to use sealed classes in your C# applications:
- Security and Immutability: Prevent others from extending your class in ways that could compromise its integrity
- Performance Optimization: The compiler can optimize certain operations with sealed classes
- Design Intent: Clearly communicate that a class is not designed to be inherited
- Final Implementation: Signal that a class represents the final, complete implementation
How Sealed Classes Work
Basic Example
Let's start with a simple example to understand how sealed classes work:
// A sealed class
sealed class Logger
{
public void Log(string message)
{
Console.WriteLine($"[LOG] {DateTime.Now}: {message}");
}
}
// This will cause a compiler error
// class CustomLogger : Logger { }
If you try to inherit from a sealed class, you'll get a compiler error like:
Error CS0509: 'CustomLogger': cannot derive from sealed type 'Logger'
Sealing a Derived Class
You can also create a class that inherits from another class, but then prevent further inheritance by sealing it:
// Base class (not sealed)
class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Animal makes a sound");
}
}
// Derived class that is sealed
sealed class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Dog barks: Woof!");
}
public void Fetch()
{
Console.WriteLine("Dog is fetching...");
}
}
// This would cause a compiler error
// class Poodle : Dog { }
Sealed Methods
In addition to sealing entire classes, C# allows you to seal individual methods in a class. This is done by applying the sealed
keyword to an overridden method:
class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Animal makes a sound");
}
}
class Dog : Animal
{
// This method cannot be overridden in derived classes
public sealed override void MakeSound()
{
Console.WriteLine("Dog barks: Woof!");
}
}
class Poodle : Dog
{
// This would cause a compiler error
// public override void MakeSound() { }
// But Poodle can still have other methods
public void PerformTrick()
{
Console.WriteLine("Poodle performs a trick!");
}
}
Practical Example: Configuration Manager
Let's look at a real-world example where a sealed class makes perfect sense:
sealed class ConfigurationManager
{
// Singleton instance
private static ConfigurationManager _instance;
// Dictionary to store configuration settings
private Dictionary<string, string> _settings;
// Private constructor prevents instantiation from outside
private ConfigurationManager()
{
_settings = new Dictionary<string, string>();
LoadSettings();
}
// Public method to get the instance
public static ConfigurationManager GetInstance()
{
if (_instance == null)
{
_instance = new ConfigurationManager();
}
return _instance;
}
// Load settings from configuration file
private void LoadSettings()
{
// Simulate loading from a config file
_settings["DatabaseConnection"] = "Server=myserver;Database=mydb;User Id=myuser;Password=mypassword;";
_settings["ApiKey"] = "abc123xyz789";
_settings["MaxRetries"] = "3";
}
// Get a setting value
public string GetSetting(string key)
{
if (_settings.ContainsKey(key))
{
return _settings[key];
}
return null;
}
}
In the example above:
ConfigurationManager
is sealed to prevent inheritance- It implements the Singleton pattern to ensure only one instance exists
- It manages application configuration securely
- There's no valid reason to inherit from this class
To use this class:
// Get the configuration manager instance
ConfigurationManager config = ConfigurationManager.GetInstance();
// Retrieve configuration settings
string connectionString = config.GetSetting("DatabaseConnection");
string apiKey = config.GetSetting("ApiKey");
int maxRetries = int.Parse(config.GetSetting("MaxRetries"));
// Use the settings
Console.WriteLine($"Database Connection: {connectionString}");
Console.WriteLine($"API Key: {apiKey}");
Console.WriteLine($"Max Retries: {maxRetries}");
Output:
Database Connection: Server=myserver;Database=mydb;User Id=myuser;Password=mypassword;
API Key: abc123xyz789
Max Retries: 3
When to Use Sealed Classes
Consider using sealed classes in these scenarios:
- Utility classes that provide static methods and don't need to be extended
- Implementation of design patterns like Singleton where inheritance could break the pattern
- Security-sensitive classes where inheritance might create security vulnerabilities
- Performance-critical classes that benefit from compiler optimizations
- Immutable objects that shouldn't be extended or modified
When Not to Use Sealed Classes
Avoid using sealed classes when:
- Your class is designed as part of an inheritance hierarchy
- You expect others to extend your class with additional functionality
- You're creating a framework or library meant to be extensible
Performance Considerations
Sealed classes offer some performance benefits because the C# compiler can optimize certain operations:
- Method calls: The compiler can sometimes devirtualize method calls for sealed classes
- Memory layout: The compiler has more knowledge about the complete structure of the class
- Inlining: The compiler can make better decisions about inlining methods
However, these optimizations are often minor, and you should use sealed classes primarily for design and security reasons rather than performance.
Example: A File Processor Case Study
Let's examine a more complex example with a file processing system:
// Base abstract class
abstract class FileProcessor
{
public abstract void Process(string filePath);
protected void ValidateFile(string filePath)
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException("File not found", filePath);
}
}
}
// Derived class for text files
class TextFileProcessor : FileProcessor
{
public override void Process(string filePath)
{
ValidateFile(filePath);
string content = File.ReadAllText(filePath);
Console.WriteLine($"Processing text file: {filePath}");
Console.WriteLine($"Text content length: {content.Length} characters");
}
}
// Final implementation for CSV files - sealed to prevent further specialization
sealed class CsvFileProcessor : FileProcessor
{
private readonly char _delimiter;
public CsvFileProcessor(char delimiter = ',')
{
_delimiter = delimiter;
}
public override void Process(string filePath)
{
ValidateFile(filePath);
string[] lines = File.ReadAllLines(filePath);
Console.WriteLine($"Processing CSV file: {filePath}");
Console.WriteLine($"Found {lines.Length} rows");
if (lines.Length > 0)
{
string[] headers = lines[0].Split(_delimiter);
Console.WriteLine($"Headers: {string.Join(", ", headers)}");
}
}
public Dictionary<string, List<string>> ParseCsvData(string filePath)
{
ValidateFile(filePath);
string[] lines = File.ReadAllLines(filePath);
var result = new Dictionary<string, List<string>>();
if (lines.Length == 0) return result;
string[] headers = lines[0].Split(_delimiter);
// Initialize dictionary with headers
foreach (string header in headers)
{
result[header] = new List<string>();
}
// Parse data rows
for (int i = 1; i < lines.Length; i++)
{
string[] values = lines[i].Split(_delimiter);
for (int j = 0; j < Math.Min(headers.Length, values.Length); j++)
{
result[headers[j]].Add(values[j]);
}
}
return result;
}
}
In this example:
FileProcessor
is an abstract base classTextFileProcessor
is a non-sealed derived class that could be further extendedCsvFileProcessor
is sealed because it represents a complete implementation for CSV files
Using the classes:
// Using the processors
TextFileProcessor textProcessor = new TextFileProcessor();
textProcessor.Process("example.txt");
CsvFileProcessor csvProcessor = new CsvFileProcessor();
csvProcessor.Process("data.csv");
// Parse CSV data into a dictionary
Dictionary<string, List<string>> csvData = csvProcessor.ParseCsvData("data.csv");
foreach (var column in csvData)
{
Console.WriteLine($"Column: {column.Key}, Values: {string.Join(", ", column.Value)}");
}
Summary
Sealed classes in C# provide a way to prevent class inheritance, which is useful in many scenarios:
- They help express design intent by indicating that a class is not meant to be extended
- They can improve security by preventing potentially harmful derivation
- They enable certain compiler optimizations that can enhance performance
- They're useful in specific design patterns and final implementations
Remember that sealing a class is a design decision that should be made deliberately. While inheritance is a powerful feature of object-oriented programming, it's not always appropriate, and sealed classes give you the ability to control it when necessary.
Exercises
- Create a sealed class called
MathUtilities
with static methods for common mathematical operations. - Implement a sealed class
ImmutablePerson
that stores personal information that cannot be changed after creation. - Design a base
Shape
class with derived classes, and decide which ones should be sealed and why. - Research and explain how sealed classes are used in the .NET Framework itself.
- Create a program that demonstrates the performance differences (if any) between sealed and non-sealed classes.
Additional Resources
- Microsoft Documentation on sealed Classes
- C# Programming Guide: Inheritance
- Design Patterns in C#
- C# Performance Tips
Understanding when and how to use sealed classes is an important part of mastering C# and object-oriented design. As you continue your programming journey, you'll develop a better intuition for when sealing a class is the right design choice.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)