Fog Creek Software
Discussion Board




Sealed classed...

Ok. I must be having one of “those” Monday mornings.

Why on earth would anyone want to seal (dotnet terminology) a class? I wanted to extend the functionality of the System.DateTime class, and after writing code for 10 minutes, I get this nice compiler message:

…cannot inherit from sealed class 'System.DateTime'

Never mind that the System.DateTime documentation doesn’t have the word sealed anywhere.

So now I just have to instantiate a date time object in my special class, and write wrapper methods, but inheriting would have been more straightforward.

Can any of you gurus shed any light on why sealing is a good idea in general and in particular to such a straightforward class (like DateTime)?!?

--
ee

eclectic_echidna
Monday, July 12, 2004

http://blogs.msdn.com/ericlippert/archive/2004/01/22/61803.aspx

Philo

Philo
Monday, July 12, 2004

BTW, I'm neither agreeing nor disagreeing - just passing along an explanation of the rationale.

Philo

Philo
Monday, July 12, 2004

I think it doesn't call it sealed because it is a struct, and not a class.

You get a different error if you are defining a struct inheriting from DateTime, rather than defining a class as you have done.

Of course, the alternative for you is not to inherit, but to have a DateTime as a private member and expose the things you want exposed.

Walt
Monday, July 12, 2004

Correct me if I'm wrong, but I think the reason DateTime isn't marked as sealed in the docs is that *all* .NET structs are always sealed and cannot ever be inherited from.

Gonna go read Philo's link now :)

Joe
Monday, July 12, 2004

It seems to me that the only reason to seal a class (or make it 'final', in Java) is to enforce a certain coding style with your objects.  That is, to force the end-user (the developer using your class) to use your class in the manner in which you intended.  This seems egotistical (although in the case of .NET classes, perhaps its got some sort of business motivation).

Basically you're saying (so it seems to me), "Hey, I gave you an interface, use MY object MY way."

Meh, what do I know.

muppet from madebymonkeys.net
Monday, July 12, 2004

Many religious debates have been waged over this, but ultimately it comes down to resources - The Microsoft team didn't have the resources to consider and plan for every possible inheritence possibility, so they made sealing the standard, with unseal inheritable classes being the exception.

Dennis Forbes
Monday, July 12, 2004

What's to plan for?  Seriously?  If I inherit from/extend your class, isn't hte liability for properly implementing overriden methods MY problem?

muppet from madebymonkeys.net
Monday, July 12, 2004

Sealed classes help prevent users from shooting their foot off by over using inheritence.  Aggregation is your friend.  Inheritence should only be used as a last resort, and must be justified.  I find that I use inheritence outside of interface implementation vary rarely in my day to day development.

Personally I think .NET puts too much emphasis on inheritence as it is.

christopher (baus.net)
Monday, July 12, 2004

"If I inherit from/extend your class, isn't hte liability for properly implementing overriden methods MY problem?"

[nod] You've hit the nail on the head.

However, how many times have all of us read articles/rants about some programming interface that didn't work the way the *author* thought it should work? (hint: search on 'propagation of nulls' and you'll find a plethora of complaints about the way nulls are implemented)

So in a perfect world developers could code productively and implementers would accept their status and who owns responsibility for certain things. In reality, anyone who produces tools generally has to code defensively, and this gets more and more true the broader the user base.

As I've said, not sure I agree, but I can understand the motivation.

Philo

Philo
Monday, July 12, 2004

From the blog entry:

> Philosophical
> 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.

And from the JOS thread linked to from the blog entry ;-)

> like the [original] poster I wanted to derive from a
> connection object to hold some additional connection
> state information

Clearly, this new connection object 'is a' OleDbConnection!  Why in the world shouldn't it be?  Do the class designers really believe they are omniscent and have the ability to foresee any and all possible uses during the design phase?

Blog:

> Practical
> Designing classes so that they can be effectively
> extended by third parties is HARD.  [...]

> 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 [...]

> I am not going to release half-baked classes that look
> extensible but in fact are not quite there.

True and admirable.  But again, how do they know up-front what is going to benefit us most?  If my company writes a Persistant Data Object component layer, am I benefited by all the snazzy new improvements to ASP.NET 2.0, like themes and master pages?  Not really...but how much developer time did they spend on that?  How much developer time are they spending on Longhorn, which isn't going to benefit anyone for at least another 5 years?

> Secure
> 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.

"Potentially hostile instances" -- what is this, the cold war?  Are the powers of evil focusing all their might on the .NET framework?  You do have to keep potential security holes in mind, but it's not like we're saying *everything* should be unsealed.

And I'm not a kindergartner, for crying out loud ;-)  If I screw with it, I expect to have to test it.  If I screw with it, and it breaks, well, that's my fault.  But don't take away my ability to do so just because you think I'm going to do something unreasonable and then complain when it doesn't work.  I expect a certain amount of responsibility to accompany an equal amount of power.

Also, making a class unsealed isn't exactly like saying "here's the source, go do what you want with it and slap it back in when you're done."  You make some methods virtual, some protected, and some private.  Same with member variables.

If MS really thinks it's just too darn much work...well, it is a lot of work!  But so is implementing a complete platform API.  "It's a lot of work, so we can never do it, ever" just doesn't cut it as an excuse when you take on a project that huge, especially when you have as many resources as MS to throw at it.

There are definately points in the framework which are very extensible and very flexible (interfaces w/ default implementations, ControlPaint class to give you default painting without forcing you to use it, etc).  But then there are other places where you just look at it and go, "uhhh, why is that class Internal/Sealed/otherwise inaccessable?"

Joe
Monday, July 12, 2004

> Sealed classes help prevent users from shooting their
> foot off by over using inheritence.

Perhaps...but it's not the responsibility of the Framework to save me from myself.  And overall, I want a viable platform, not a playground sandbox.

Some things, like garbage collection, are reasonable to take off the shoulders of the developer.  If someone doesn't like the GC in a particular situation, they are free to write code in C/C++ and use [DllImport] to their heart's content.

But with this issue, the *only* option you're left with is aggregation.  It's a pain in the arse to wrap a class with lots and lots of methods, plus no matter what you do you lose 'is a' type compatibility, which is a huge restriction.  So in the end, while MS's API's may be pristine, your API's end up looking like they were kludged together with silly putty when you try to work around sealed classes.

Joe
Monday, July 12, 2004

In Java, the Date class is not final.  In fact, it's extended by java.sql.Timestamp (JDBC), among others.  The consensus that I've heard is that this is generally a Bad Thing - equality rules don't work correctly (a.equals(b), but !b.equals(a)), they have different precision, etc.

Also, a non-final class can't generally be regarded as immutable, either - java doesn't have this problem, as Date isn't immutable (although it should be).

.NET may not have gotten it right either, but making new mistakes is generally preferred over re-making someone else's old mistakes.  :)

schmoe
Monday, July 12, 2004

For what it's worth, sealed also gives the opportunity for extra compiler optimizations: replacing virtual function calls with direct calls, for example, because the compiler knows that the method cannot ever be overridden.

I think the MS folks are generally ok with sealed because that was the norm in COM - inheritance simply wasn't an option. I would argue that the problems with the interfaces come not from the class being sealed, but because there are methods that take concrete class types rather than interfaces. If they'd defined the interface, you'd be all set.

Why, oh why, is there no System.Web.UI.IPage interface??

Chris Tavares
Monday, July 12, 2004

> I think the MS folks are generally ok with sealed because
> that was the norm in COM - inheritance simply wasn't an
> option

That's why I switched to Java pre-.NET ;-)  I like all the OOP goodness.

Good points on the compiler optimization though, especially when you consider the primitives as structs.

Joe
Monday, July 12, 2004

> a.equals(b), but !b.equals(a)

What's a and what's b here?  If a is Date, and b is Timestamp, then !b.equals(a) is the fault of whoever implemented Timestamp, right?  Of course if !a.equals(b), then there's a problem...

Joe
Monday, July 12, 2004

I think sealed makes a lot of sense.

Think about the use case where you have classes that you are distributing outside your organization, in the context of a very large system.  ie, you have your own platform that you've built on the .NET framework, that provides APIs through its class hierarchy.  Like, for instance, an enterprise-level doc management system with a Document class and manager classes.

I think just for the sake of overall system stability you don't want programmers defining their own subclasses of your core (first-class) data objects and dumping them into the system.

The arguments that it's "my inheritance, my responsibility" hold up fine when you're the "owner" of the system.  But when you're interfacing with a platform that's running across the whole company via inheritance, that's different.  It's a valid safeguard there.

indeed
Monday, July 12, 2004

Indeed, could you provide an example where someone subclassing your Document class would have adverse effects on the system?

Joe
Monday, July 12, 2004

Joe:

Well, for instance if there's a persistence layer that calls on the objects to write themselves into a DB.  I think if the object's serialization isn't implemented properly, then it could cause problems.

Arguably the data validation rules would be good enough to prevent such problems, but that depends on the data model--if a Document modeled a high-level data model (eg CAD), then it's much harder for the low-level persistence layer to enforce integrity.  So it's helpful to prevent people from putzing around by defining their own classes.

And again, if it's a live system that everyone is using, and you don't own the underlying platform/codebase, you really may not have any business extending it.  Or worse, you may not be able to properly unit test if you do extend.

Additionally:  sealed also gives organizations a big-brotherish way to control the evolution of their systems.

Here's a simple example:

If you have a strictly-defined data model, and you keep the sealed classes in source control so that only the "architect" has write access to them, then the junior programmer can't start making his own subclasses and extending the data model at will.  I definitely think there's real value there; it can be awfully hard to get people (especially inexperienced folks) to heed the abstractions in place.

indeed
Monday, July 12, 2004

"it's not the responsibility of the Framework to save me from myself"

No, but if you do something based on an assumption about the way the code "should" work, and your app runs, you will sent it to the customer.  Then MS comes out with Windows 2010 and has to change their own code to fix something and all of a sudden YOUR app doesn't work any more.  Does the customer come to you and demand a new app? No.  They whine about the new OS they got that doesn't run their favorite app any more.

Steamrolla
Monday, July 12, 2004

Indeed --

Actually, in the case of the Document object, I would argue that it should *not* be sealed.  What if the person using your API really does need to implement a Document subclass that would represent a CAD document?  Or another kind of doc?  Are you going to provide all those implementations?  If they extend it, it's their responsibility to make sure it serializes properly, isn't it?

Your second example makes a lot of sense in some scenarios.  For something used internally, sealed helps a lot there.  But for someone like MS making a large widely distributed framework, it seems to make more sense to make it as extensible as possible.  Besides, using sealed in such a way really represents working around a social problem by creating a technical road block.  A better solution would be to enforce the coding standards with reviews and other development practices.

Joe
Monday, July 12, 2004

Steamrolla -- then isn't that the framework attempting to save itself from itself and its own designers?

Regardless of if you use inheritance or not, something someday will change that causes your app to break or otherwise misbehave.  This happens all the time when you have to code around existing bugs or flaws.

I do agree that sealed improves compatibility, backwards and forwards, especially when things are new and changing.  But I don't think it's a good long-term solution.  I think by V2.0 we should be able to start looking at the framework and say "this is stable, this isn't" and begin to open things up a bit more...

Joe
Monday, July 12, 2004

"Sealed classes help prevent users from shooting their foot off by over using inheritence."

Oh good god.

What's that phrase: "Languages for the rest of them"?

You know, I'm really starting to think this industry would be much improved by shooting fully 90% of the developers.

God: first it was multiple inheritance we're all too stupid to be trusted to use, now it's *INHERITANCE*.

Katie Lucas
Monday, July 12, 2004

well.. there ARE an awful lot of stupid developers around....

muppet from forums.madebymonkeys.net
Monday, July 12, 2004

"What if the person using your API really does need to implement a Document subclass that would represent a CAD document?"

That's a big what-if--directly extending first-class objects in an enterprise data model==and it needn't always be done through inheritance.

In the enterprise systems I've seen, "soft inheritance" through metadata on the object is much preferred.

More major changes should indeed be well-guarded, and then it's feasible to just recompile without the sealed keyword.  We are talking about run-time linked systems after all.

"Besides, using sealed in such a way really represents working around a social problem by creating a technical road block."

Sure.  And people do it all the time with computer security.  We put forth technical roadblocks (passwords, access control, etc.) to control a social problem (hacking, data theft, etc.).

Sealed in this context is just code security.  Like other forms of security, it cannot solve 100% the underlying social problems.  But it can mitigate them.

So I don't see the justification for saying "we should throw out sealed altogether, and enforce coding standards etc. instead."  We can very well do both.

indeed
Monday, July 12, 2004

`In the enterprise systems I've seen, "soft inheritance" through metadata on the object is much preferred.'

What the fuck does this mean? Seriously?

The word "enterprise" is one of the most abused and misused words in this profession, and every crack-pot just throws together a couple of weak premises, under the Enterprise banner, and acts haughty and uppity about it. What utter claptrap.

.
Monday, July 12, 2004

Indeed -- I guess I still don't understand understand why it seems so detrimental to have someone extend this root Document object.  The way I'm picturing this API, is that it defines only a generic Document, and then consumers of the API could subclass it to store various specific document types.  What's so awful about that?

As for social engineering via imposing technical restrictions, I believe it is a necessary evil in *some* situations, but not all.  In security, yes, obviously, because you are dealing with malicious, somewhat anonymous hackers. 

But a data access API?  c'mon...  Clearly you need security to restrict who can access what data, but simply allowing another developer to extend your Document object shouldn't have any effect on that.

I still think if you're trying to use language constructs to control your own developers, you've got bigger problems.  And in MS's case, more work should go into making sure the framework classes are extendable, instead of copping out and just tossing sealed around willy nilly.

To be clear, I'm not saying sealed is useless or even a bad thing *in general.*  I just think it's being abused in terms of the .NET Fx libraries.

Joe
Monday, July 12, 2004

> Can any of you gurus shed any light on why sealing is a good idea in general and in particular to such a straightforward class (like DateTime)?!?

See http://msdn.microsoft.com/library/en-us/csspec/html/vclrfcsharpspec_1_8.asp

One reason I can think of is that when a struct is passed as a parameter it's passed by value (not passed by referece) ... so if you did have a DerivedDateTime and passed it to a method that expected a DateTime then you'd get "object slicing" anyway (the callee would receive a DateTime, not a DerivedDateTime) ... or, you wouldn't be allowed to define new data members in your derived struct (because the classes that you're calling only allocate a certain stack size for the parameter they're receiving) ... or, the size of a struct would need to be read from the instance (not known at compile-time).

Christopher Wells
Monday, July 12, 2004

Joe:

Well I won't speak for the .NET libraries, but I'm just putting forth my view on sealed from personal experience.  I guess you're right--there are bigger problems if one needs to control developers with sealed.  And that big problem is inexperience and over-eagerness.

I think as a middle ground, it's at least useful to wean the organization onto the view of a carefully-evolved system vs. willy-nilly extension. :)

indeed
Monday, July 12, 2004

I am in the camp that says that inheritence was a bad idea.  It is the worse kind of coupling there is.  Basically a class becomes a global namespace, with very few pre-condition rules.  Anybody that inherits from your class can pretty much muck around with the internal state of your class why not really knowing what they are doing.  Sure you could make all your members private, but then you should be using aggregation and not inheritenc.  My ultimate language would only allow interface inheritence and aggregation. 

I believe there is no such thing as an "is-a" relationship, there is only "behaves like."  Immensly complicated architectures can be developed using only this construct.  I've seen inheritence hierarchies totally collapse on themselves in large production code bases.  COM failed in implementation complexity, but in concept I believe it is the correct way to define subsystems. 

My guess MS would have left inheritence out of .NET, except there is still a huge contigent of programmers that think any real programming language must support inheritence.  Instead they make it impossible to inherit from certain classes.  This is the next best to leaving inheritence out the framework all together.

I haven't used .NET for that long, but I have to admit that MS has done a lot right here.  It feels like a better Java to me.  If you know me, you'd understand that says a lot. 

christopher (baus.net)
Monday, July 12, 2004

Clearly, some folks forget how many types of "strings" exist in the Win32 API. If sealing a class means that there will only ever be one System.String and one System.DateTime, I'm all for it.

Ankur
Monday, July 12, 2004

A lot of the .NET classes have their source code available online (see MONO and ROTOR).

So, if you don't like something, just re-write it yourself.

That's what Rockford Lhotka ( http://www.lhotka.net/ ) did in his CSLA framework ( http://www.lhotka.net/ArticleIndex.aspx?area=CSLA%20.NET ).

I seem to remember he wanted to extend the DateTime struct to cater for nulls.

Steve Jones (UK)
Tuesday, July 13, 2004

Rocky's SmartDate isn't a struct, it's a class (though he admits that was an oversight).

And he didn't rewrite any of the .NET code - he just put a wrapper around a DateTime struct.

Not to say that you couldn't do such things, but Rocky wasn't doing them, last time I looked.

Dave Hallett
Tuesday, July 13, 2004

"My guess MS would have left inheritence out of .NET, except there is still a huge contigent of programmers that think any real programming language must support inheritence. "

This is absolutely insane, and _obviously_ incorrect - Microsoft has made extensive use of inheritence in the .NET framework (don't be confused by the use of sealed - sealed isn't a condemnation of inheritence, but rather is Microsoft saying "we'll keep it to ourselves"). Of course subclasses are coupled, but remember that just because coupling is bad in some situations most certainly doesn't mean that it's bad in all situations.

If you really think implementation inheritence is an evil then I think that either you just can't make the mental leap, or you've never seen it used properly (vaguely claiming that large systems collapse on themselves offers absolutely no proof of anything except that you can think of arbitrary, manufactured examples to support your belief).

Dennis Forbes
Tuesday, July 13, 2004

And it's been mentioned that it may/may not be a good idea to seal base classes - but it seems some people really got carried away.  Little things like every shipped web part (presentation layer classes) being sealed in SharePoint, but the data layer objects (the lists) are completely inheritable.  Not complaining about the inheritable lists, but what kind of mindset came up with the strategy of "allow them to make their own custom lists to store the information they need and we'll continue to show our default view - but if they need a single custom behavior on the UI - make them re-implement everything in the object."

Unfocused Focused
Tuesday, July 13, 2004

> I think as a middle ground, it's at least useful to wean
> the organization onto the view of a carefully-evolved
> system vs. willy-nilly extension. :)

I have no problem w/ MS using sealed as an intermediate, temporary solution to keeping complexity down and compatibility up while the system evolves...I just hope it doesn't stay that way forever :)

> Anybody that inherits from your class can pretty much
> muck around with the internal state of your class why
> not really knowing what they are doing.  Sure you could
> make all your members private, but then you should be
> using aggregation and not inheritenc

Ummm, no.  You should put some forethought into your class design.  This is why members in C# are not virtual by default.  You have to consciously *choose* to allow a subclass to override or even have access to a particular base class feature.

It's all about the extensibility points.  Decide in what ways it makes sense to allow your class to be extended, and design for it.

Joe
Tuesday, July 13, 2004

Also, let's not forget that in .NET the reflection API lets us invoke any member of any object -- including private methods, if we so choose.  If someone *really* wants to muck with the internal state of your object, they're going to find a way...so isn't it better to define a standard way of augmenting behavior via a well-documented inheritance contract?

Joe
Tuesday, July 13, 2004

"I believe there is no such thing as an "is-a" relationship, there is only "behaves like.""

No, it's is-substitutable-for.  Cline has an excellent explanation for this relationship:

"Here's how to make good inheritance decisions in OO design/programming: recognize that the derived class objects must be substitutable for the base class objects. That means objects of the derived class must behave in a manner consistent with the promises made in the base class' contract." (from the C++ FAQ Lite)

I think as long as you hold to the rule above, inheritance logically _can't_ lead to additional complexity, unless the new functionality has side effects outside of the class contract's logical constraints.

Of course that means (again, as Cline pointed out) that a lot of decisions made by class designers about inheritance are wrong.  But a lot of that goes back to ye olde "trying to model the world" problem, which is abundant with composition as well.

indeed
Tuesday, July 13, 2004

*  Recent Topics

*  Fog Creek Home