23

This question came to mind after reading the answer to this question; which basically made the point that List<T> has no virtual methods, since it was designed to be "fast, not extensible".

If that's the design goal, why didn't the original design including sealing the class? (I know that's not possible now, seeing how that would break a lot child classes within client code)

Community
  • 1
  • 1
Pierreten
  • 9,917
  • 6
  • 37
  • 45
  • 1
    I guess it's one of the (many?) poor design decisions in .NET? – user541686 Jan 25 '11 at 05:22
  • It's probably the same reason why TcpListener doesn't implement IDisposable, but TcpClient does. Hindsight is 20/20... – cdhowie Jan 25 '11 at 05:23
  • 6
    It is still useful to inherit `List` to create a specialized collection and *add helper methods* (e.g. `List.Count(Color)`), but it may be a good idea to `seal` some methods (`Add`, etc). Also, While .Net as a few quirks, overall it is a consistent framework. Besides - can't you ask the same question on all non-sealed classes with non-virtual methods? – Kobi Jan 25 '11 at 05:30
  • 3
    I wouldn't call it a poor design decision -- more like a non-decision. Nobody ever said "Hey, let's seal this class" so it isn't. Is there any harm in it? – Gabe Jan 25 '11 at 05:33
  • 1
    @Gabe: Well, considering the various enterprise-level codebases I've worked with, making it sealed would certainly have made my life a bit easier... – cdhowie Jan 25 '11 at 05:35
  • 1
    @Kobi: Personally I would place such helper methods on a static class and make them extension methods against `IList`, `ICollection`, or `IEnumerable`. No need to derive, and then you can apply such methods to *any* list/collection/enumerable of the proper element type, instead of just instances of the helper class. Such classes usually smell of poor design. – cdhowie Jan 25 '11 at 05:40
  • 1
    @cdhowie - That's hardly the same. I still want a consistent type across my code (A `ColorBox`, in my example, instead of `List` everywhere) - you can complicate the example with a few extra members and properties (`Owner`, for example). In addition, remember extension methods didn't exist when C# 2.0 came out. (I also try not to create extension methods on my types, I try to use them on types I can't expand otherwise, but that's another discussion) – Kobi Jan 25 '11 at 05:48
  • 1
    @cdhowie: `List` shipped well before extension methods ever existed. Besides, what's the alternative? Somebody who poorly inherits from `List` will likely implement the alternative poorly too. – Gabe Jan 25 '11 at 05:51
  • @cdhowie @Kobi: Also such `ColorBox` probably implements some other interfaces, that makes things easier than `List`. – Cheng Chen Jan 25 '11 at 05:53
  • @Gabe: Someone who can't code well with either approach *probably* shouldn't be coding... "Noob coder X will bork it either way" isn't really a good method for making design decisions. – cdhowie Jan 25 '11 at 05:56
  • 1
    @cdhowie - Exactly! That's a perfectly good argument not to make the whole framework sealed - you should not protect inexperienced programmers from making these mistakes - people should know what they're doing. – Kobi Jan 25 '11 at 06:11
  • Using sealed classes/methods always feels like somebody else taking decisions in my code without knowing what my code is doing. C# would be better if the sealed keyword did not exist. – David Heffernan Jan 25 '11 at 09:29

2 Answers2

15

There's no compelling reason to seal it. It does no harm to derive from it. I used to be of the opposite mindset - only leave things unsealed that you intend for people to derive from. But in hindsight, it makes no sense. .NET takes the position that methods are non-virtual by default but classes are unsealed by default. List<T> just follows that same practice.

Where you would want to seal a class is when it does override virtual methods but further subclassing is not easy or obvious. It can be slightly useful to derive from a collection such as Dictionary<TKey,TValue> to stick in known type parameters and avoid typing them out if used in an application. For example maybe you would have a QueryString class that derives from Dictionary<String,String>.

And since there's no virtual methods, there's really nothing to protect the class against by sealing it.

Josh
  • 68,005
  • 14
  • 144
  • 156
  • Two counter-points: (1) You can seal overridden members, so sealing a derived class that overrides methods is not strictly necessary -- just seal the methods you overrode, possibly also overriding any you didn't just so you can seal them. (2) Given that the one case you think sealing a class is appropriate in actually isn't really necessary, would you conclude that the sealed class attribute has no value? – cdhowie Jan 25 '11 at 05:38
  • I wouldn't say it has no value, but there are plenty of cases where it is used unnecessarily. But having said that, although you can seal methods, you can't "seal" constructors without sealing the class. Plus the sealed keyword is a nicely enforced declaration that you don't intend for the class to be derived from. – Josh Jan 25 '11 at 05:43
  • Well, constructors can't be overridden, so sealing them would not make any sense. Additionally, I'm having trouble reconciling your most recent statement *"the sealed keyword is a nicely enforced declaration that you don't intend for the class to be derived from"* with this one in your answer: *"I used to be of the opposite mindset - only leave things unsealed that you intend for people to derive from. But in hindsight, it makes no sense."* – cdhowie Jan 25 '11 at 05:46
  • I'm not sure what you mean. All I said was that I used to "seal by default" and now I no longer do. As I said there's still cases where I would want to prevent deriving from, such as in the case of a Prism module. But as far as `List` goes (and other generic collections) I have no problem with the decisions made by the BCL team. – Josh Jan 25 '11 at 05:51
  • Ok, I just wanted to clarify your answer since I apparently got a different initial impression than you intended. – cdhowie Jan 25 '11 at 05:57
2

There must be many reasons why they decided not to make List<T> sealed, however, one possibility is that the Framework design team wanted parity with ArrayList (which is not sealed) so that existing programs and Frameworks which had designs based on extending ArrayList would be able to more easily upgrade their designs to use List<T>. Making List<T> sealed would have been a real dead-end for these uses and one of the strong guiding design choices would have been to allow people to easily upgrade their existing code-bases from ArrayList to List<T>.

Tim Lloyd
  • 37,954
  • 10
  • 100
  • 130
  • There's no reason they had to actually *decide* to not seal `List`, as unsealed is the default. All they had to do was *not* decide. Also, they designed `Collection` to be derived from so that it could take the place of `ArrayList`. – Gabe Jan 25 '11 at 17:01
  • @Gabe No quite. Even if they *wanted* to seal `List` they would have potentially caused aggravation for users\framework developers who had relied on extending ArrayList. This is something they would have to include in their decisions making. I hear you re. `Collection` which is interesting. Whilst you can add behaviour to a `List` by inheriting and extending, you cannot properly override behaviour. You are correct, `Collection` is much better suited to overriding behaviour. I was referring more to extending behaviour. You make a good point though. – Tim Lloyd Jan 25 '11 at 17:22