Why Are So Many Of The Framework Classes Sealed?


I talked earlier this month about some issues in subclassing, and recommended sealing your classes. A Joel On Software reader asks why Microsoft ships so many sealed classes in the framework.  The poster said: “I’ve yet to see a reasonable explanation about why they limited flexibility in such a way.

Well, every public class that my team produces is sealed if possible.  If it is not possible to seal a class then, if possible, it has an inheritance demand on it so that only someone with the MSFT private key can subclass it.  My reasons for insisting upon this policy boil down to one overriding principle:

Good code does exactly what it was designed to do, no more, no less.

Let me expand upon that in four ways.

1) Philosophical.  OOP design includes subclassing to represent the polymorphic “is a” relationship between two things.  A Giraffe IS AN Ungulate IS A Mammal IS AN Animal…  Unless I can think of a clear case where a customer would need to express an IS A relationship with some code that I produce, I don’t allow for such cases.

2) Practical.  Designing classes so that they can be effectively extended by third parties is HARD.  (Look at the collection base classes for example.) You have to get the design right — what is protected?  You have to implement that design correctly. The test matrix grows enormously because you have to think about what weird things people are going to do. You have to document the protected methods and write documentation on how to properly subclass the thing. 

This is all expensive and time consuming — that is time that we could be spending looking for bugs in more important user scenarios, planning future versions, fixing security holes, whatever.  There is only a finite amount of developer time we can spend on designing and implementing code, so we have to spend it the way that benefits customers most.  If the class is not designed to be extended, I’m going to avoid all that expense by sealing it.  I am not going to release half-baked classes that look extensible but in fact are not quite there.

3) Compatible.  If in the future I discover that I should have sealed a class, I’m stuck.  Sealing a class is a breaking change.  If I discover that I should have left a class unsealed, unsealing in a future version is a non-breaking change.  Sealing classes helps maintain compatibility.

4) Secure. the whole point of polymorphism is that you can pass around objects that look like Animals but are in fact Giraffes.  There are potential security issues here.

Every time you implement a method which takes an instance of an unsealed type, you MUST write that method to be robust in the face of potentially hostile instances of that type.  You cannot rely upon any invariants which you know to be true of YOUR implementations, because some hostile web page might subclass your implementation, override the virtual methods to do stuff that messes up your logic, and passes it in.  Every time I seal a class, I can write methods that use that class with the confidence that I know what that class does.


Now, I recognize that developers are highly practical people who just want to get stuff done.  Being able to extend any class is convenient, sure.  Typical developers say “IS-A-SHMIZ-A, I just want to slap a Confusticator into the Froboznicator class”.  That developer could write up a hash table to map one to the other, but then you have to worry about when to remove the items, etc, etc, etc — it’s not rocket science, but it is work.

Obviously there is a tradeoff here.  The tradeoff is between letting developers save a little time by allowing them to treat any old object as a property bag on the one hand, and developing a well-designed, OOPtacular, fully-featured, robust, secure, predictable, testable framework in a reasonable amount of time — and I’m going to lean heavily towards the latter.  Because you know what?  Those same developers are going to complain bitterly if the framework we give them slows them down because it is half-baked, brittle, insecure, and not fully tested!   

Comments (27)

  1. Well said. Thank you for the explanation. I’ll remember it the next time I hear someone whining and moaning about this. :-)

  2. Ricky Dhatt says:

    Ingo Rammer has an article on why many of the Windows Forms classes are sealed:

    http://www.ingorammer.com/Articles/SealedIsGood.html

  3. Handy info! I do hope nobody wants to extend the Giraffe class. Override on the NumberOfLegs property or something? <g>

  4. Philip Rieck says:

    Not to whine and moan — I agree with much of what Ingo Rammer wrote in the link above, and much of this article — but…

    This is excellent information and an excellent philosophy for most development teams.

    However, when we’re talking the BASE class library different factors come into play. And as much as Windows Forms and ASP.NET are extensions to that, they are vital extensions that help me do my job (thanks guys!)

    Yes, designing classes so that they can be extended is hard, but that’s why we don’t all write the BCL/FCL. In fact that’s the whole point of it. .Net is such a productive environment because of all the hard work that I don’t have to do — you did it. That’s why I pay you $2500/year+, and consider it cheap.

    Yes, there is finite time. But making a class and the functionality it encapsulates easily extendable is just as much a feature as making menus have gradient backgrounds, and I’ll argue to which one is more important.

    If you ever say "No developer will ever want to replace this feature with something else", you’re wrong. I’m sure of it. It may only be one, and yes, you have to weigh that — but that one developer definately will whine at you. It may be me.

    Also on the last point about security — if you can write methods that take sealed classes without worrying about "hostile instances" (I like that phrase), then isn’t unsealing a class a breaking change? If I write code that assumes ImageList is sealed, then you unseal it, you’ve introduced potential security holes in my code.

  5. Eric Lippert says:

    Re: gradiant menus: You’re preaching to the choir here!

    Re: base class libraries: yes, there certainly are portions of the library which are there solely because they need to be extended. But there is a big difference between "must be extended, by design" and "might be extended, maybe, someday, by someone, perhaps." To put the work involved in the former to achieve the latter is not the best way to spend limited effort.

    Re: "no developer" — sure, its likely that given any task, someone will want to do it at some point — but you remember what Aesop said about pleasing all of the people all of the time? You’re not paying us to be all things to all people, you’re paying us to come up with the best balance of features for the platform as a whole.

    Re: breaking change — sorry, I was not clear. ANY change ANYWHERE in the code can cause SEMANTIC breaking changes. After all, changing the code changes its behaviour!

    By "breaking change" I almost always mean "a change that will make code that used to compile now not compile". That is, I’m talking about SYNTACTIC breaking changes.

  6. Peter Torr says:

    Not just fail to _compile_, but fail to verify as well (assuming you don’t have SkipVerification). And in case anyone cares: http://weblogs.asp.net/ptorr/archive/2004/01/15/58902.aspx

    I was also thinking of writing a short (ha!) blog on language-compiler-enforced rules versus runtime-enforced rules, but it’s kind of obscure.

  7. Philip Rieck says:

    True on all counts (except I’d rather instead of pleasing everyone you pleased me, but I’ll pitch that idea to Mr. Gates next time he calls me to see how I like Microsoft. )

    I’m not arguing to harass you here, as I can understand all of the reasons you posted (although #2 gets the lions share of the credit in my mind) — I just want to make sure to keep you guys on your toes.

    I guess the thing that made me want to raise my voice was the line "If the class is not designed to be extended, I’m going to avoid all that expense by sealing it". I just want to make sure that the extensibility scenario is made (or stays) a first-class concern during the design. It’s a very important feature to me, and many others like me (Insane German-Scottish-Americans who own a hermit crab — we’re an important demographic!)

    And Mr. Torr — please do write that if you have the urge. You can tell by the past popularity of Chris Brumme’s blog that the community loves obscure technical information.

  8. You make some good points about why classes should be sealed, private, protected, or whatever. And the Ingo Rammer article makes some further points about why classes which straddle the managed/unmanaged divide are sealed.

    It all makes perfect sense, ….., until that moment when you want to extend or override the class in some fashion.

    Take, for example, the CollectionEditor class. This is a class that contains a private nested form class.

    Firstly, its been designed such that the form class in inaccessible at runtime, and instead one has to go through all kinds of hoops to create a design time lookalike environment to get a colelction editor up at all. Secondly, its been designed with virtually no extensibility in mind at all. Something as simple as a valiadtion step (OnClosing) cannot be achieved with the class as it stands.

    The choices are stark – reimplement the class from scratch, or seek refuge in unsavoury practices using reflection.

    I agree that everything should be sealed, private, whatever unless its been designed for extension and reuse, but I also suggest that everything in the BCL should be designed for extension/reuse unless theres a damn good reason not to.

  9. Eric Lippert says:

    > everything in the BCL should be

    > designed for extension/reuse

    > unless theres a damn good reason

    > not to

    We could do that. But that would then present us with a choice of three options. Which do you like best?

    1) cut some other features that some users want

    2) do a shoddy job

    3) ship years late

    Because of "fully featured", "well written" and "on time", you only get to pick TWO.

  10. Dan Shappir says:

    While I fully agree with the "time is money" argument, which is at the root of most of your assertions, I do think that there are some potential answers to the dilemma:

    1. Inheritance is not the only means for extending existing functionality. The GoF book for example discourages inheritance and provides lots of alternative design patterns for this purpose.

    Another example: STL collections are extensible in that you can roll-your-own collection and use it in the provided algorithms. Yet inheritance isn’t used at all.

    2. Some features provided by some programming languages do make it easier to use inheritance in a safer way. Specifically DBC. See languages such as Eiffel and D.

    3. You could provide two implementation of various classes: one sealed and optimized, the second open and safe. Obviously this could double your development time, but it would satisfy many of the other constraints.

    The bottom line: through proper design, and using the appropriate tools, even complex systems can be made "fully featured", "well written" and "on time". It’s not easy though, which is why almost nobody ever does.

  11. Eric Lippert says:

    It’s certainly true that "time is money", but my argument is more general. What I’m getting at is more "available resources are finite, potential features are infinite, so choose wisely what you implement".

    Clearly this subject has touched a nerve. I’d like to cover it in more detail later, particularly the differences between sealing a class (preventing inheritance) and sealing a method (preventing overriding). There are ways to achieve many of my desired goals by good use of method sealing without preventing inheritance. But I’ve spent too much time on this subject already for this week.

  12. Steve says:

    Many developers do not even know of many basic OO principles such as Open/Close principle, Law of Demeter, Liskov Substitution principle, etc., yet wouldn’t hesitate to inherit.

    They could likely accomplish the needed effect by using object composition (huh, what’s that?)instead.

  13. Eric Lippert says:

    Indeed — and even developers who know those principles may not know them by name.

    This morning, I could have told you that extensibility is good, simple interactions between objects are good, and breaking semantic changes with virtual methods are bad.

    But before I refreshed my memory (thanks, google!) just now, I doubt that I could have told you that a breaking semantic change in a virtual method violates the "Liskov Substitution Principle". It’s a lot more important to know the principle than it is to know its name.

    Developers are pragmatic people — I spend most of my time developing simple, "flat" object models, not reading up on theory of OO design. And I don’t think I’m alone in that regard!

  14. Steve says:

    Stating the principles by name was simply a quick way to make a reference, the same way we say "singleton" to reference a pattern. So I think there is value in knowing the name.

    "…not reading up on theory of OO design. And I don’t think I’m alone in that regard!"

    You’re probably not alone and don’t have to because you have it ingrained, but a lot of people don’t and that’s my point — they probably should read up on theory of OO design; it’s not just theory, it’s practical!

  15. In The Trenches says:

    Eric, thanks for your accessibilty via these blogs. I had a rant all prepared to paste in but I think I’ll just say two things:

    1) "System.Web.UI.WebControls.ListItem" – a 3 legged giraffe. Can’t be derived from nor altered with attributes – big step back.

    2) You’re overlooking MGMT101 when time is of the essence: Prioritize! Fire all those guys translating to Lithuanian and hire a programmer who can add attributes to a ListItem.

  16. Dan Golick says:

    I think you can only take this position that everything should be sealed because you own the source code. Not only can we developers not extend the classes we cannot cut and paste copy them to make our own versions. (Although we can decompile them.)

    Ingo gives some good reasons for sealing classes but this is not a default "design rule" but a reasoned decision.

    If we look at the implementation of the BCL we see that many win32 classes are wrapped. But this can only be done because the interface was rich enough to allow you to accomplish the wrapping.

  17. Darren Oakey says:

    I always find these arguments funny. I’m on the fence with the sealed/unsealed debate, but I find it amusing that people use inheritance to extend functionality, and still think they are ok coders…

    Let’s just think a little.. I think both these statements are fairly unambiguous?<br/>

    a) looser coupling = better code..<br/>

    b) inheritance = tightest possible coupling construct. <br/>

    So – a little logical concatenation, and we immediately conclude inheritance as a general technique is BAD.. A program that chooses interface inheritance + composition over inheritance will always be quicker to develop and more maintainable than one that uses inheritance.<br/>

    (Not that inheritance is always bad, I use it a lot… but only strictly in the framework/is-a style..)

  18. . says:

    … then some zoologist notices that the NumberOfHorns property in Giraffe class always returns 2, because the original developer was not aware of giraffes having any number of them from 2 to 5, and is stumped because the class is sealed.

    Protecting yourself from malicious user? Put a filter between you and user, like giving him an interface and not a class instance. Protecting the user from his own hypothetical action? No way, you shouldn’t tell anyone "you can’t do it because I thought of everything there is to have". Sealing a class is the thinly disguised equivalent of the infamous "everything that can be invented has been invented" phrase from 1880s.

  19. Darren Oakey says:

    you are still completely failing to understand – you don’t seal because "you’ve thought of everything", you seal because you haven’t. Because in that case, suppose giraffe wasn’t sealed. And (remembering of course we don’t have the SOURCE to giraffe) someone goes along and overrides the thing that returns the number of horns to five….

    But… the programmer was lazy, and in one obscure function, that WE didn’t even know was there, called CalculateHornDetails – a programmer hasn’t been very smart. Since they wrote the code, and "KNOW" there are only two horns, they made a fixed length string field to store the output of the function. This is actual vital for a few of the people who are using your code, because they are big into horn analyis… – they are using your library quite happily in production, and have been for months.

    You realise you need more horns.. so you override it, and that’s the object you return in your "GetGiraffe()" function…

    Suddenly, two systems you’ve never heard of are falling down in production, and you don’t have a clue why.

    You might think that’s a silly example, but it’s not. It’s the life I as a technical architect lead every day. I use libraries I don’t have the source to, and I have NO IDEA who is using the system libraries I produce.

    Because I’m lacking both the requisite infinite knowledge, and also perfection (bummer) – there is NO WAY I can safely predict what’s going to happen if people do certain things. I NEED the ability to change the libraries without informing people (they’re my libraries after all, and I need to fix bugs, refactor, and extend them) – and I get in serious trouble if I make a change which breaks something that currently works.

    So… Because I’m really not that bright, it would just be a case of russian roulette to release unsealed code and say "go for your life!" – because one of these days, it wouldn’t be THEIR live’s (or jobs) on the line – it would be mine!

  20. Josh Mouch says:

    How about this simple and completely logical case?

    I would like to know at what point in my code the SortExpression Property of the  System.Web.Ui.DataSourceSelectArguments is being changed to an incorrect value.  Since I don’t own the DataSourceSelectArguments code, the next logical step is to inherit from DataSourceSelectArguments, override SortExpression, and then set a breakpoint on its property setter, or add a trace or logging or something.

    This is an extremely simple way to track down a problem, but it just simply cannot be done because DataSourceSelectArguments is sealed.  The only alternative is to go through ALL of your code.  Which is better?

    After you find the problem, you can even delete your extended class.  The point is just that it saved you hours of time.

  21. Ken Johnson says:

    Your rationale seems to have several serious flaws in it:

    1) Your description of inheritance as an IS-A relationship, while common is really not the correct way of looking at OO inheritance.  A BEHAVES-AS-A relationship is more accurate.  This article on the Liskov Substitution Principle explains it pretty well: (PDF) http://www.objectmentor.com/resources/articles/lsp.pdf

    2) You seem to think you know every way a developer is going to use your API. This is wrong.  For example, We use Test-First Development, the excessive use of sealed classes makes this much more difficult.  Additionally in cases where we ARE supposed to extend an API class (an event handler for example); the methods all take concrete, sealed classes (not interfaces or abstract classes we could create mock objects for to test our event handler with).

    3) Sealing a class does not make it secure.  Preventing someone from extending your class (or creating a mock of it for testing) is not a substitute for writing your classes in secure way.  If you have important pre-conditions for a method, then check for them at the beginning of a method.  If you have a method that should not be called from derived classes — then make it private.  Lack of time to do things right is not a valid excuse for doing things wrong.