Be more productive with more keystrokes: seal all the things

TL;DR: Declare your classes (and records!) sealed by default.

In my last post about keystrokes and productivity, I recommended typing less in favor of default access modifiers. In this post I will propose typing more to override a default.

I am talking about sealing classes by default. C#’s default is for classes (and by extension non-struct records) allowing inheritance. Just writing class will allow anyone with access to it (nitpick: and access to one of its constructors) to inherit from it. This may seem great for reusability and mockability. However, designing classes for inheritance is difficult. This is true for entity/value types, where a correct implementation of Equals and GetHashCode is challenging. It is also true for services, where enforcing behavioral invariants becomes difficult, and implementation details can be unintentionally leaked.

Take a class that implements IDisposable and references managed resources only (the 99 % case). Implementing IDisposable on a sealed class is trivial: just call Dispose on the disposable instances that the class owns. Done. Compare that to a correct base class implementation. And the inheriting classes still have to do the right thing at the right time. Highly error-prone. For more details on that can of worms, check out this great, but also hard-to-find article on disposal (and finalization) by Joe Duffy.

There are respected members of the .NET community who wished that sealed were the default. The argument for sealing by default is stronger for libraries than your own internal applications, because libraries are used and abused by other people. When someone inherits in a wrong way from your non-sealed classes that were not designed for inheritance, the best-case scenario would be the whole thing blowing up into their faces. Exceptions all around. The worst-case scenario would be the class pretending to work, but silently making wrong business decisions.

As in my current project I have to provide a lot of libraries to other teams, I tend to play it safe and seal everything by default. I have not encountered any requests for unsealing yet, nor have I unsealed anything myself just for unit-testing purposes. Composing instead of inheriting is here to stay and will make long inheritance chains an ever rarer sight.

This means that usually nothing is lost by sealing your classes by default. You gain the freedom of changing your implementations without breaking other people’s code and the time not spent on explaining and documenting how to inherit correctly from your classes.

So, just type that extra sealed.

(If you had the habit of writing default access modifiers on classes, you can think of it as replacing that old habit with the new habit of writing sealed ;-).)