Page 3 of 5
Sealed – the death of inheritance
If you are considering inheritance there is another access modifier – sealed. If you apply the sealed modifier to a class then the class cannot be used as a base class for a new class. Many programmers think that sealed is a great idea because they don't want to make a gift of their class to use as the basis for something new – but software reuse is the basis of the object-oriented method and using sealed breaks this. Just like private and protected, sealed is not a code security measure – it is easy enough to decompile a sealed class.
That is if you define two classes as:
public sealed class MyClass1
You will see a compile time error saying that you cannot derive from a sealed type.
Many, perhaps most, of the classes that make up the .NET framework are sealed and this is a mistake as it causes many a programmer to have to reinvent the wheel when a derived class would be the simplest way of creating something that does the job.
Notice that you can add new behaviour to any class using extension methods but this isn't as powerful or elegant as using inheritance.
The only good reason for using sealed is if the class you have just created contains something low level that would most likely break a class that extended them – however it is difficult to find any really convincing example. Even so some programmers, and the .NET design team in particular, take the attitude that all of their public classes should be marked as sealed.
You could then ask why C# supports inheritance at all if every class has inheritance turned off? Indeed as discussed in the introduction many programmers are of the opinion that inheritance is not the best way to reuse code. Interface inheritance and composition, for example, are design alternatives to inheritance that are simpler to manage.
It is also argued that allowing classes that are in the framework to be extended is inviting misuse and hence bugs that would bring the entire system into disrepute. The effort needed to create classes that can be safely extended by inheritance in a manner that stops the inexperienced from doing damage is just too much. Far better to have a library of code that can be used in a narrow, well-defined, way with a high degree of confidence that the code does exactly what it claims to.
Another reason for sealing a class is that it permits the compiler to perform optimisations by making all of the methods non-virtual. So using sealed as a default option is easy, safe and rewarding - to the class implementer not as much the class consumer.
When inheritance isn't possible
On the other hand if the code doesn't quite do what you want, it isn't quite complete, and adding some additional methods and properties would make it just right then to discover that the class is sealed is annoying. The only solutions to the problem involves a lot of work. You can recreate the code for the base class and then extend it – a task that is relatively easy if you have the source code of the base class. Even if you are able to implement a copy and paste form of source code inheritance you have made the system more error prone by there being two copies of essentially the same code.
A better approach is to use object composition or containment. Simply create the object that needs to be extended as an object property of a new class and then provide methods that that mirror the methods of the contained class and which simply call the contained class's methods. Because the containing class's methods call the contained class's methods to get things done this is also called the delegation pattern.
For example if you have the class:
public sealed class MyClass1
then it can be extended using containment:
public class MyClass2
In a more complex case you would have to add methods and properties corresponding to all of the methods and properties exposed as public by the contained class – and this could be a lot of work.
You can also make use of a constructor to create the contained class and this opens up the possibility of dynamically selecting the class to be contained.
There is also the lazy way of achieving containment – to allow the contained object to be public. For example:
public class MyClass2
With this change you no longer have to implement methods and properties to re-expose the contained class's members you simply have to use a double qualified name as in:
MyClass2 myObj = new MyClass2();
The disadvantages of this approach are fairly obvious and if you really want to create a new class that looks and behaves in a similar way to an existing class then simply making the contained class public isn't good enough.
All of this seems like a lot of trouble to go to just to defeat "sealed". Indeed many of the approaches are reinventions of types of programming that inheritance was supposed to replace. The key issue is that with a sealed class there can be no new classes in the type hierarchy that derive from it and as such no complications with polymorphism and references can always be resolved at compile time.
There is a second and less well known use of the sealed modifier. You can use sealed on a method that overrides a virtual method. This stops the chain of overriding of virtual methods at the first derived class that applies sealed. For example given the class:
public class MyClass1
then a derived class can override the virtual method:
public class MyClass2:MyClass1
but, as the sealed modifier has been applied, a class derived from MyClass2 cannot override MyMethod1.
Of course, if you want to stop the chain of virtual overriding at the first class, e.g. MyClass1, simply don't declare the method as virtual!