Verketten von Strings in C#: + Operator vs. String.Concat vs. StringBuilder

Ich wage zu behaupten dass im Quellcode nahezu jeder Softwareanwendung einzelne Strings zu einem gemeinsamen String verkettet werden. Eine Verkettung von Zeichenfolgen zu implementieren ist somit etwas absolut rudimentäres und sollte zum Repertoire jedes Softwareentwicklers gehören. In C# stehen aber mehrere Möglichkeiten bereit um Strings zu verbinden. Und entsprechend viele Meinungen bestehen darüber welches die richtige oder beste Möglichkeit ist. In diesem Zusammenhang habe ich es schon des Öfteren erlebt, dass ein erfahrener Programmierer einen C# Anfänger eine Moralpredigt darüber gehalten hat, einen StringBuilder statt den + Operator einzusetzen. Aber ist dies wirklich immer die beste Lösung?

In diesem Artikel möchte ich auf die verschiedenen Möglichkeiten zum Verketten von Strings eingehen. Ich erläutere Ihnen dabei die grundlegenden Unterschiede, gebe Hinweise welche Möglichkeit wann eingesetzt werden kann und runde das Ganze mit einem kleinen Performancevergleich ab.

 
+ Operator

String Variablen sind unveränderlich. Bei der Verkettung mittels des + Operators muss daher jeweils eine neue Zeichenfolge erzeugt werden. Betrachten wir beispielhaft eine Verkettung von String A, B und C in der Form „A = A + B + C“.  Hierbei können B und C nicht einfach an A angehängt werden. Es wird vielmehr so vorgegangen, dass neuer Speicher in der Gesamtlänge der beiden Werte A und B reserviert wird und die Inhalte der Werte A und B in diesen neuen Speicher kopiert werden. Das gleiche Verfahren wird danach zum Verketten dieses Zwischenergebnisses mit dem String C durchgeführt. Bei der Verkettung vieler Strings entstehen daher viele temporäre Objekte die unnötig Speicher belegen. Ausserdem ist diese Variante auf Grund der vielen Kopiervorgänge relativ langsam.

 
String.Concat

Mittels der Funktion String.Concat lassen sich ebenfalls Strings verketten. Dabei werden der Funktion alle Zeichenfolgen übergeben. Das obige Beispiel lässt sich daher folgendermassen umstellen: „A = String.Concat(A,B,C)“. Bei der Funktionsausführung wird zuerst die Gesamtlänge des neuen Strings anhand der Längen aller übergebenen Werte ermittelt. Danach wird einmalig Speicher reserviert und die Strings werden kopiert. Die Funktionalität ähnelt somit der des + Operators, mit dem entscheidenden Unterschied das die vielen Zwischenvariablen wegfallen da nur einmalig neuer Speicher reserviert und genutzt wird.

 
StringBuilder

Die StringBuilder Klasse verwaltet Speicher um Strings darin aufzunehmen. Dazu erzeugt die Klasse ein grosses Char Array. Dieses wird nach und nach mit den übergebenen Werten gefüllt. Die Anfangsgrösse des Arrays kann angegeben werden. Beim Hinzufügen von Zeichenketten wird das Array wenn nötig automatisch vergrössert. Die Klasse StringBuilder hat somit genau wie die String.Concat Funktion den Vorteil, dass nur einmalig Speicher reserviert werden muss und dieser für mehrere String Verkettungen genutzt werden kann. Der daraus resultierende Performancegewinn kann aber durch die nötigen internen Verwaltungsoperationen aufgehoben werden oder sogar in einer Verschlechterung hinsichtlich Ausführungsgeschwindigkeit resultieren.

StringBuilder und String.Concat unterscheiden sich in einem entscheidenden Punkt. Bei der String.Concat Funktion müssen direkt alle zu verkettenden Werte angegeben werden. Bei der StringBuilder Klasse können diese Werte nach und nach mit der Add Funktion hinzugefügt werden. Und genau da liegt auch der Einsatzbereich dieser Klasse. StringBuilder sollte nur dann zum Einsatz kommen wenn mehrere Werte verbunden werden sollen, welche aber nicht in einem einzigen Aufruf angegeben werden können. Andernfalls ist zumeist die String.Concat Funktion vorzuziehen.

 
Performancevergleich bei fixen Werten

Nach der Theorie soll nun ein kleiner praktischer Test folgen um die Performance der einzelnen Funktionen zu testen. In einem ersten Test werden jeweils vier feste Zeichenfolgen verkettet. Dies erfolgt 10 Millionen Mal. Dabei wird die Ausführungszeit mit der Klasse Stopwatch gemessen. Der nachfolgende Quellcode zeigt zur besseren Lesbarkeit eine verkürzte Version des Testprogramms ohne Zeitmessung und ohne Ausgabe der Ergebnisse.

    1 int i;

    2 int count = 10000000;

    3 string value = null;

    4 StringBuilder builder = null;

    5 

    6 for (i = 0; i < count; i++)

    7 {

    8     value = "Hello" + " " + "World" + "!";

    9 }

   10 

   11 for (i = 0; i < count; i++)

   12 {

   13     builder = new StringBuilder(25);

   14 

   15     builder.Append("Hello");

   16     builder.Append(" ");

   17     builder.Append("World" );

   18     builder.Append("!");               

   19 

   20     value = builder.ToString();

   21 }

   22 

   23 for (i = 0; i < count; i++)

   24 {

   25     value = string.Concat("Hello"," ","World","!");

   26 }

 
Betrachten Sie bitte erneut die theoretischen Angaben aus den vorhergehenden Abschnitten. Welche Reihenfolge bezüglich Ausführungsgeschwindigkeit erwarten Sie in diesem Test?

Ich würde erwarten, dass auf Grund der wenigen Kopiervorgänge die String.Concat Funktion die schnellste Ausführungsgeschwindigkeit hat. StringBuilder sollte langsamer sein wegen des höheren Verwaltungs-Overheads in der Klasse. Der + Operator wird wahrscheinlich im Mittelfeld oder auf dem letzten Platz landen. Je nachdem wie sich im StringBuilder die eingesparten Kopiervorgänge und der Verwaltungs-Overhead auswirken.

Die folgenden Messergebnisse haben sich bei der Programmausführung ergeben (Mittelwert aus 10 Messungen):

–          + Operator: 0.022 Sekunden

–          StringBuilder: 0.697 Sekunden

–          String.Concat: 0.392 Sekunden

 
Wie erwartet ist String.Concat bei den wenigen Zeichenfolgen schneller als StringBuilder. Aber was ist mit dem + Operator? Dieser wurde um ein vielfaches schneller ausgeführt als die beiden anderen Varianten. Eigentlich müsste die Ausführungszeit wegen der vielen Kopiervorgänge doch zumindest über der von String.Concat liegen.

Die Erklärung für die kurze Ausführungszeit des + Operators ist simpel. Der Compiler führt Code Optimierung durch. Er erkennt das fixe Zeichenketten verkettet werden sollen und ersetzt diese daher gleich durch die entsprechende zusammengesetzte Zeichenkette. Eine Analyse des IL Codes zeigt das der Compiler die Zeile „Hello“ + “ “ + „World“ + „!“ direkt in „Hello World!“ umgewandelt hat. Eine Verkettung fixer Zeichenfolgen wird somit bereits bei der Kompilierung durch eine bereits verkettete Zeichenfolge ersetzt. Dies erklärt die hohe Geschwindigkeit im Vergleichstest.

 
Performancevergleich bei variablen Werten

Nach dem ersten Test und der betrachteten Code Optimierung durch den Compiler, stellt sich natürlich die Frage wie sich die Ausführungszeiten bei variablen Zeichenketten verhalten. Daher wurde das Testprogramm angepasst und die einzelnen Verkettungsfunktionen wurden mit variablen Werten ergänzt. Der nachfolgende Quellcode zeigt wiederum eine verkürzte Version des Testprogramms. Die Variablen value1 und value2 sind Funktionsparameter. Bei dem Funktionsaufruf wurden die Werte „abc“ und „def“ übergeben.

    1 int i;

    2 int count = 10000000;

    3 string value = null;

    4 StringBuilder builder = null;

    5 

    6 for (i = 0; i < count; i++)

    7 {

    8     value = "Hello" + value1 + " " + value2 +

    9             "World" + value1 + "!" + value2;

   10 }

   11 

   12 for (i = 0; i < count; i++)

   13 {

   14     builder = new StringBuilder(25);

   15 

   16     builder.Append("Hello");

   17     builder.Append(value1);

   18     builder.Append(" ");

   19     builder.Append(value2);

   20     builder.Append("World");

   21     builder.Append(value1);

   22     builder.Append("!");

   23     builder.Append(value2);

   24 

   25     value = builder.ToString();

   26 }

   27 

   28 for (i = 0; i < count; i++)

   29 {

   30     value = string.Concat(

   31         "Hello", value1, " ", value2,

   32         "World", value1, "!", value2);

   33 }      

 
Die folgenden Messergebnisse haben sich bei der Programmausführung ergeben (Mittelwert aus 10 Messungen):

–          + Operator: 1.568 Sekunden

–          StringBuilder: 1.052 Sekunden

–          String.Concat: 1.557 Sekunden

 
Überaschenderweise ist der StringBuilder an erster Stelle gelandet. Diese Klasse scheint sehr effizient zu arbeiten so dass der interne Veraltungs-Overhead bereits bei diesen wenigen zu verkettenden Zeichenketten kompensiert wird. Der + Operator liegt in diesem Test nur knapp hinter der String.Concat Funktion. Höchstwahrscheinlich wird dieser Abstand aber mit zunehmender Anzahl an Zeichenfolgen grösser werden.

 
Fazit

In C# stehen mehrere Möglichkeiten zur Verkettung von Zeichenfolgen bereit. Es gibt dabei keine Variante die generell zu bevorzugen ist. Je nach Anwendungsfall gibt es aber besser und schlechter geeignete Varianten.

Die folgenden Grundsätze erleichtern die Auswahl der Programmiermethode.

Anwendungsfall 1: Es werden nur wenige Strings verkettet und der Speicherverbrauch und die  Performance spielen dabei eine untergeordnete Rolle.

–          Hier gilt: Lesbarkeit vor Performance und Speicherverbrauch. Es sollte die Variante gewählt werden die am besten lesbar ist.

Anwendungsfall 2: Es werden viele Strings verkettet so dass dies Einfluss auf Performance und Speicherverbrauch hat. Hierbei sollte die Auswahl der geeigneten Variante davon abhängen was für Werte verkettet werden sollen.

–          Verkettung fixer Werte: + Operator verwenden

–          Verkettung variabler Werte: String.Concat verwenden

–          Verkettung von Werten die nicht in einem gemeinsamen Aufruf angegeben werden können: String.Builder verwenden

Werbung
Dieser Beitrag wurde unter .NET, C# abgelegt und mit , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden /  Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s