Top 10 Fehler–#13: Missbrauch von String-Operationen

11. September 2014

Dies ist Teil 14 der kleinen Serie, die ich als Kommentar zum Blog-Beitrag Top 10 Mistakes that C# Programmers Make begonnen habe.

Wir sind bei den Themen angekommen, die mir in der Liste noch fehlen. Heute ein Evergreen Zwinkerndes Smiley

 

AJ’s Common Mistake #13: Misusing string operations

Es ist Grundwissen für jeden .NET-Entwickler: Ein String kann nicht verändert werden → wenn ich mehrere Strings verknüpfe muss eine neuer String gebaut werden mache ich das exzessiv, dann entstehen unnötig temporäre Objekte, die die Speicherverwaltung unter Druck setzen also bitte StringBuilder nutzen…

Und so heißt es dann auch in Improving .NET Application Performance and Scalability:

Excessive string concatenation results in many allocation and deallocation operations, because each time you perform an operation to change the string, a new one is created and the old one is subsequently collected by the garbage collector.

Auf der andere Seite gibt es dann noch String.Format (immer an die CultureInfo denken Zwinkerndes Smiley), das sehr gerne benutzt wird, um komplexer formatierte Strings aufzubereiten.

Theoretisch alles kein Problem.

 

Und die Praxis?

Da findet man Aussagen wie folgende:

“The StringBuilder.Append() method is much better than using the + operator.” (so)

“When concatenating more than three dynamic string values, use StringBuilder.” (so)

“String type in C# being immutable therefore the preferred way of string manipulation is through StringBuilder. […] Of course for simple usage where performance isn’t too much of a concern use + or concat.” (so)

“string concatenation is extremely slow, because .NET creates extra copies of your string variables between the concat operations, in this case: two extra copies plus the final copy for the assignment.” (so)

I propose String.Concat uses StringBuilder so I think that performance argument fails here” (so)

Oder solche Perlen (aus einem Review, das ich kürzlich gemacht habe, auf das Notwendige reduziert):

   1: string.Format("{0}{1}", s1, s2))

   2:  

Oder Hilfsmethoden wie diese (aus dem Gedächtnis zitiert):

   1: string StringFormat(string format, string[] args)

   2: {

   3:     StringBuilder sb = new StringBuilder();

   4:     sb.AppendFormat(format, args);

   5:     return sb.ToString();

   6: }

Und wenn man dann nach dem Sinn fragt, kommt oft genug das (falsch verstandene!) Performance-Argument (das zudem nie belegt werden kann – wie lautet das Mantra jeder Optimierung? Messen! Messen! Messen!).

 

Die Fakten…

Solange eine Verkettung von Strings in einem Ausdruck passiert gilt:

  • Konstante Literale (also “Vorne ” + ” Hinten”) werden vom Compiler zusammengefasst, Laufzeitauswirkung gleich null.
  • Wenn Variablen ins Spiel kommen ist die Verwendung von “+” die effizienteste Variante. Der Compiler macht daraus String.Concat-Aufrufe, diese Methode direkt zu verwenden macht also technisch keinen Unterschied, schadet aber i.d.R. der Lesbarkeit. Und String.Concat kommt in diversen optimierten Varianten, die sowohl bzgl. Performance, als auch bzgl. Speicherbedarf das Optimum herausholen.

Die Verwendung von StringBuilder ist absolut sinnvoll, wenn komplexe Strings aus vielen Einzelbestandteilen in mehreren Aufrufen zusammengebaut werden. Anders ausgedrückt: Für eine Hand voll Verknüpfungen ist das Overkill. Wenn der Einsatz eines StringBuilder tatsächlich Sinn macht, sollte man ihm dann aber auch unter die Arme greifen, und ihm von Anfang an eine sinnvolle Kapazität spendieren.

Der folgende Hinweis unter Performance Considerations ist übrigens überholt:

“New data is appended to the buffer if room is available; otherwise, a new, larger buffer is allocated, data from the original buffer is copied to the new buffer, and the new data is appended to the new buffer.“

Das war in frühen .NET-Versionen so, mittlerweile nutzt StringBuilder eine verkettete Liste von Buffern und vermeidet so das Kopieren und den LOH.

String.Format schlussendlich macht absolut Sinn wenn man Texte aufbereiten muss die separat im Code abgelegt oder in Ressourcen ausgelagert sind. Ebenso, wenn die Platzhalter zusätzlich mit Formatanweisungen versehen sind.

Wenn man andererseits einfach nur Textfragmente aneinander hängt ist String.Format gegenüber “+” laufzeitmäßig deutlich im Nachteil. “Deutlich” sollte man aber relativ sehen. Bei einzelnen Aufrufen würde ich der Lesbarkeit immer den Vorzug geben. Erst wenn es um häufige Aufrufe geht, also z.B. in Schleifen, würde ich mir darüber Gedanken machen.

Wo String.Format zum echten Problem werden kann, sind Aufruf zum Logging. Zum einen sind diese Aufrufe dann sehr häufig, zum anderen sehe ich oft Code der den Aufruf von String.Format macht, selbst wenn das Logging gerade abgeschaltet ist. (Das sehen andere genauso.)

 

Zusammenfassend…

In den allermeisten Fällen macht man wenig falsch, wenn man der Lesbarkeit des Codes den Vorzug gibt. Also keine Angst von “+”. Davon abgesehen liegen die typischen Anwendungsgebiete für StringBuilder und String.Format eigentlich auf der Hand.

Wer hingegen mit dem Performance-Argument kommt, der sollte zumindest wissen, wie es um die Performance tatsächlich bestellt ist. Fakten statt Halbwissen ist das eine, Zahlen das andere (z.B. hier oder hier). Die beste Zusammenfassung dazu findet sich (IMHO) übrigens – wie so oft – bei Jeff: It. Just. Doesn’t. Matter!