9

I'm wondering this, since I need to inherit from StringBuilder to implement a TextChanged event. I could always make a wrapper containing a private StringBuilder and implicit/explicit conversions, but this does not seem like a proper solution.

Luckily I can inherit from the object that is writing to the StringBuilder, so this is not really a problem for me, but I'm still curious as to why that class is sealed.

KappaG3
  • 660
  • 5
  • 14
  • 2
    Why do you need to handle a `StringBuilder`'s "TextChanged" event? You are the one who adds text to it. I assume that performance considerations outweigh extensibility for it. – Tim Schmelter Aug 02 '13 at 20:06
  • @Tim What if it is a `StringWriter`, or some native function like `SendMessage`? Also, events are generally nicer to work with than having to write code like `void WriteToBuilder(StringBuilder sb, string text)`. – KappaG3 Aug 02 '13 at 20:09
  • 2
    ["Design and document for inheritance or else prohibit it"](http://martinfowler.com/bliki/DesignedInheritance.html) – Sergey Kalinichenko Aug 02 '13 at 20:11
  • 1
    See also Eric Lippert's blog post, [Why Are So Many Of The Framework Classes Sealed?](http://blogs.msdn.com/b/ericlippert/archive/2004/01/22/61803.aspx) – Brian Aug 06 '13 at 13:00

2 Answers2

15

This is a bit difficult to answer. The upvoted answer has a problem, StringBuilder doesn't have any virtual methods. So there's nothing you could do to break the class or doing anything "extra" unsafe.

I think the likely reason is that the CLR has special knowledge of the class. That's a bit mundane for StringBuilder, compared to other .NET types it is intimate with, the pinvoke marshaller knows what the class looks like. You use it when you need to pass a string reference to unmanaged code, allowing it to write the string content. Necessary because that's not legal for String, it is immutable. The pinvoke marshaller knows how to set the internal members of StringBuilder correctly after the pinvoke call. But wouldn't know how to do that for your derived class. That slicing risk is not exactly worth the benefit of not sealing it. Particularly since it doesn't have virtual methods so you cannot override its behavior at all.

An extension method is otherwise a very reasonable workaround.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • it might not have anything that is virtual but it has something far far worse... directly public members that are marked `internal`, not sure how the CLR would resolve that... or if it would prevent a derived class from modifying them. – Mgetz Aug 02 '13 at 22:18
  • That's something *very* different, it just means that the method is implemented in C++ instead of C#. Backgrounder [is here](http://stackoverflow.com/questions/8870442/how-is-math-pow-implemented-in-net-framework/8870593#8870593) – Hans Passant Aug 02 '13 at 22:29
  • "StringBuilder doesn't have any virtual methods" sounds a bit wrong. Every object includes virtual methods for equals/hashcode and overriding this (especially for string) may cause whole system to crash. – Kaan Yy May 28 '15 at 20:46
  • You are quibbling. None of the methods that make it do what it does are virtual. – Hans Passant May 28 '15 at 20:49
7

StringBuilder is sealed because it assembles strings, e.g. there should never be a reason to inherit from it because any use should be limited in scope. StringBuilder is not a replacement for string and should never be used that way. The goal of the class was to have a way of easily handling any operations that required mutable strings without performance penalty. As such There is no way inheriting from StringBuilder would provide any utility and would create possible security issues as the class deals with mutable strings.

Take a look at the reference source it is not a simple class but a tightly focused utility. Furthermore it does things that are unsafe, and thus allowing inheritance would allow modification of unsafe code which could compromise security.

Mgetz
  • 5,108
  • 2
  • 33
  • 51
  • 2
    I see. But then again, couldn't they simply seal the *dangerous things to override*? There's no need to completely seal the class. Also, most of its members seem to be internal, so inheriting wouldn't even show them. – KappaG3 Aug 02 '13 at 20:18
  • @KappaG3 did you take a look at the class... `unsafe` is used in almost every function. The goal was speed with security, not extensiblilty as it was assumed (quite correctly as I still haven't seen a use case) that nobody would ever need to extend it. – Mgetz Aug 02 '13 at 20:20
  • 4
    @KappaG3: because `StringBuilder` is intended to use inside methods to solve particular task - build string here and now. It even shouldn't be a part of any type's public contract. – Dennis Aug 02 '13 at 20:21
  • Hm, got it. Thank you for explaining, your answer is pretty clear now. – KappaG3 Aug 02 '13 at 20:23
  • @Dennis: If a method is going to be called repeatedly for the purpose of building up a long string, what type would be better than `StringBuilder` for such purpose? I suppose if `StringBuilder` had implemented an interface `IStringAppendable` with members `Append` and `AppendFormat`, that interface type might have been better in an API than `StringBuilder`, but I don't think `StringBuilder` implements any such interface, does it? Is there any other type that would work better? – supercat Aug 02 '13 at 22:17
  • @supercat: TextWriter. One of its descendants is a wrapper around SB. – Dennis Aug 03 '13 at 04:34
  • @Dennis: That might be a reasonable choice, though it would seem to add a bit of overhead. It would seem like there should have been an `ITextWriter` interface, which StringBuilder could have implemented, but alas that's not how it works. – supercat Aug 03 '13 at 13:23
  • One potential reason to inherit from `StringBuilder` is when you want a `AppendIfHasValue()` method instead of writing if statement every time, but you don't like extension methods. Wrapping is a solution, but you have to manually expose all the method of wrapped `StringBuilder`, which doesn't sound good. – Roman Gudkov May 14 '19 at 09:55