Why are sealed classes faster in C#? And should I seal them?

12/05/2022

Sealing a class means basically it is forbidden to inherit from that specific class. If you try anyway you will get a compiler error. Where this is the main benefit you also get some performance improvements with it. This article will show you why plus you get my personal take on sealed classes. One word of caution: The performance benefits are very very small and in almost all cases neglectable. You could I ask, why I am explaining that stuff then. Well because I like those nerdy stuff a lot and I'd like to know what is going on in the runtime when I do certain stuff.

virtual functions

The reason one would open a class for inheritance is so that someone else can change the behavior of that class and can use it's "internal" state. To offer this we can use the virtual function. If you are not familiar with virtual functions I got you covered here.

Why should a sealed class be faster than a class with virtual functions? The answer is simple: We don't have to check which function we have to call. You see if we have something like this:

BaseClass baseClass = new DerivedClass();
baseClass.DoVirtualFunction();

The runtime has to check whether it has to call BaseClass.DoVirtualFunction or DerivedClass.DoVirtualFunction() and this costs time. If you are interested how this looks in code you can go here to sharplab.io.

is and as

These two keywords perform a check if we can cast a object to a specific type. Now why should checks against a type have a performance benefit if the type is sealed.

Have a look at the following example:

public class MyType
{
}

MyType a = CreateMyType();
var isDisposable = a is IDisposable

Now pretty obvious that isDisposable is always false. Unfortunately no it is not. If we extend the example a bit we can see the following:

public class MyType { }
public class DerivedType : MyType, IDisposable {}

MyType a = CreateMyType();
var isDisposable = a is IDisposable

Then there is a chance that CreateMyType returns DerivedType which in fact is a IDisposable. If we would have a sealed MyType we can omit the runtime check because we know at compile-time that this comparison is false.

The performance implications are really small on this one:

public class VirtualFunctionBenchmark
{
    private static readonly BaseClass _derivedClass = new DerivedClass();
    private static readonly SealedClass _sealedClass = new();

    [Benchmark]
    public bool Is_NonSealed() => _derivedClass is IDisposable;

    [Benchmark]
    public bool Is_Sealed() => _sealedClass is IDisposable;
}

public class BaseClass { }

public class DerivedClass : BaseClass, IDisposable
{
    public void Dispose() { }
}

public sealed class SealedClass { }

Results:

|       Method |      Mean |     Error |    StdDev |    Median |
|------------- |----------:|----------:|----------:|----------:|
| Is_NonSealed | 0.0375 ns | 0.0259 ns | 0.0590 ns | 0.0061 ns |
|    Is_Sealed | 0.0061 ns | 0.0092 ns | 0.0082 ns | 0.0024 ns |

The check is 5x faster when we handle with a sealed class. But be aware, even the "non-optimized" version is only 0.04 nanoseconds.

Now the central question: Should I seal them?

sealed via default

Now this is my very own personal take and please take it with a grain of salt: I would prefer if classes are sealed by default and you have to explicitily allow a class to be inherited from. Why do I say that? Because it gives performance improvements? Well no. Where "free" performance is nice that is not my main take. Getting a proper API design right is not so trivial. There are many aspects one has to consider when you "open the door" for everyone. I know I am a .NET guy, but still I have to quote from a Java book:

Design and document for inheritance or else prohibit it

And that makes obviously to me sense. There are some key features I do love about sealed classes:

  • As said, getting the design right is hard. You don't want to have a security class where key details can get overwritten and compromise the security. If you want to have a simpler example: Try to get Equals right with inheritance.
  • A non-sealed class CAN'T guarantee immutability.
  • Overuse of inheritance. I saw projects with 7 levels of inheritance and a lot of switch cases. Yes that seems counter-intuitive but it exists because it is hard to change the API later on.
  • Plays nicely with Composition over inheritance
  • Modifying the behavior or structure of your class can potentially break subclasses

But that is just my take on the topic.

Conclusion

If we use sealed type the runtime can remove or simplify some checks for use with which it can give us better performance. But more important with the sealed keyword we can express our intend.

0
Buy Me a Coffee at ko-fi.com
An error has occurred. This application may no longer respond until reloaded. Reload x