C# Sealed Class: When and Why to Use It
c# sealed class: Learn how the sealed modifier prevents inheritance, affects performance, and guides class design in C# with practical examples.
When you mark a class with the sealed modifier in C#, you prevent any other class from deriving from it. That single rule influences design decisions, runtime behavior, and long-term maintainability. The c# sealed class concept is often mentioned in passing, but its practical implications deserve closer attention.
The Effect of the sealed Modifier
The sealed modifier is a compile-time constraint. It tells the compiler that the class cannot be used as a base class. Any attempt to inherit from a sealed class produces a compile-time error. This is different from abstract, which forces inheritance, or static, which cannot be instantiated. A sealed class can be instantiated normally; it just cannot be extended.
public sealed class Configuration { public string ConnectionString { get; set; } } // Compile error: cannot derive from sealed type 'Configuration' public class DerivedConfiguration : Configuration { }
The error message is clear: you cannot derive from a sealed type. This is a deliberate design choice that makes the class's behavior final.
Sealing Override Methods
The sealed modifier is not limited to classes. You can also seal an individual override method or property in a derived class. This stops further overriding in the next level of inheritance.
public class BaseService { public virtual void Handle() { } } public class DerivedService : BaseService { public sealed override void Handle() { } } public class MoreDerivedService : DerivedService { // Compile error: cannot override inherited member because it is sealed public override void Handle() { } }
Sealing an override is useful when you want to preserve a specific behavior in a part of the hierarchy while still allowing other virtual members to be overridden.
When a Sealed Class Is the Right Design Choice
Sealing a class communicates intent. It says that the class was designed without extension points and that deriving from it would likely break invariants. This is common for value objects, DTOs, or classes that encapsulate a specific algorithm.
Use a sealed class when:
- The class represents an immutable value or a fixed set of data.
- The class contains complex initialization logic that should not be altered.
- You want to prevent subclassing for security or correctness reasons.
- The class is a leaf in an inheritance hierarchy.
A typical example is a settings class that reads from a configuration source. Subclassing it could allow overriding validation or default values, which might lead to inconsistent behavior.
public sealed class AppSettings { public string ApiEndpoint { get; } public int TimeoutSeconds { get; } public AppSettings(string apiEndpoint, int timeoutSeconds) { ApiEndpoint = apiEndpoint; TimeoutSeconds = timeoutSeconds; } }
Performance Implications of Sealed Classes
One reason developers reach for sealed is performance. In the .NET runtime, virtual method calls require a lookup through the virtual method table. When the JIT compiler sees a sealed class, it can sometimes devirtualize the call, replacing the indirect call with a direct one. This can reduce overhead, especially in hot paths.
However, the actual benefit depends on the runtime version and the calling context. The JIT may already devirtualize calls to non-sealed classes when it can prove the actual type. Sealing a class makes that proof easier, but it is not a guarantee. You should not seal a class solely for performance without measuring. The design benefits of sealing are often more important than the micro-optimizations.
Common Misconceptions About Sealed Classes
A frequent misunderstanding is that a sealed class cannot be instantiated. That is false. A sealed class can be instantiated normally; it just cannot be inherited. Another misconception is that sealing a class prevents the use of interfaces. A sealed class can implement interfaces, and you can still use it polymorphically through those interfaces.
public sealed class Logger : ILogger { public void Log(string message) { } }
The class is sealed, but it still works with dependency injection and interface-based design.
Sealed Classes and Records
C# records can also be sealed. A record already provides value-based equality, and sealing it prevents derived records from altering that behavior. This is useful when you want to define a canonical data shape that should not be extended.
public sealed record Point(int X, int Y);
Sealing a record is a strong statement about the intended usage. It ensures that any code expecting a Point will always receive the exact type, not a subclass with additional properties.
Compatibility and Maintainability Tradeoffs
Sealing a class is a binary decision. Once you seal a class, you cannot unseal it without breaking existing code that may have tried to inherit from it. In a library, sealing a public class can limit consumers who might want to extend it. On the other hand, leaving a class unsealed commits you to supporting extension points that may be misused.
When designing a public API, consider whether you want to allow third-party extensions. If you are unsure, it is often safer to leave the class unsealed and document that it is not intended for inheritance. Sealing later is a breaking change; unsealing later is also a breaking change for anyone who assumed it was sealed. The decision should be made based on the expected evolution of the codebase.
The sealed modifier also has an effect on testability. Sealed classes are harder to mock in some testing frameworks that rely on inheritance-based proxies. If you use a mocking library that requires non-sealed classes, sealing a class may force you to use the real implementation or introduce an interface. This is a practical consideration that often influences whether a class should be sealed.