Skip to main content

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:

csharp
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:

  1. Security and Immutability: Prevent others from extending your class in ways that could compromise its integrity
  2. Performance Optimization: The compiler can optimize certain operations with sealed classes
  3. Design Intent: Clearly communicate that a class is not designed to be inherited
  4. 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:

csharp
// 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:

csharp
// 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:

csharp
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:

csharp
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:

  1. ConfigurationManager is sealed to prevent inheritance
  2. It implements the Singleton pattern to ensure only one instance exists
  3. It manages application configuration securely
  4. There's no valid reason to inherit from this class

To use this class:

csharp
// 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:

  1. Utility classes that provide static methods and don't need to be extended
  2. Implementation of design patterns like Singleton where inheritance could break the pattern
  3. Security-sensitive classes where inheritance might create security vulnerabilities
  4. Performance-critical classes that benefit from compiler optimizations
  5. Immutable objects that shouldn't be extended or modified

When Not to Use Sealed Classes

Avoid using sealed classes when:

  1. Your class is designed as part of an inheritance hierarchy
  2. You expect others to extend your class with additional functionality
  3. 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:

  1. Method calls: The compiler can sometimes devirtualize method calls for sealed classes
  2. Memory layout: The compiler has more knowledge about the complete structure of the class
  3. 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:

csharp
// 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:

  1. FileProcessor is an abstract base class
  2. TextFileProcessor is a non-sealed derived class that could be further extended
  3. CsvFileProcessor is sealed because it represents a complete implementation for CSV files

Using the classes:

csharp
// 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

  1. Create a sealed class called MathUtilities with static methods for common mathematical operations.
  2. Implement a sealed class ImmutablePerson that stores personal information that cannot be changed after creation.
  3. Design a base Shape class with derived classes, and decide which ones should be sealed and why.
  4. Research and explain how sealed classes are used in the .NET Framework itself.
  5. Create a program that demonstrates the performance differences (if any) between sealed and non-sealed classes.

Additional Resources

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! :)