String-Ausgabe: Format oder Concat in C #?


177

Angenommen, Sie möchten Zeichenfolgen ausgeben oder verknüpfen. Welchen der folgenden Stile bevorzugen Sie?

  • var p = new { FirstName = "Bill", LastName = "Gates" };

  • Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

  • Console.WriteLine(p.FirstName + " " + p.LastName);

Verwenden Sie lieber das Format oder konzentrieren Sie sich einfach auf Zeichenfolgen? Was ist Ihre Lieblings? Tut einer davon deinen Augen weh?

Haben Sie rationale Argumente, um das eine und nicht das andere zu verwenden?

Ich würde für den zweiten gehen.

Antworten:


88

Versuchen Sie diesen Code.

Es ist eine leicht modifizierte Version Ihres Codes.
1. Ich habe Console.WriteLine entfernt, da es wahrscheinlich einige Größenordnungen langsamer ist als das, was ich zu messen versuche.
2. Ich starte die Stoppuhr vor der Schleife und stoppe sie direkt danach. Auf diese Weise verliere ich nicht an Präzision, wenn die Ausführung der Funktion beispielsweise 26,4 Ticks benötigt.
3. Die Art und Weise, wie Sie das Ergebnis durch einige Iterationen geteilt haben, war falsch. Sehen Sie, was passiert, wenn Sie 1000 Millisekunden und 100 Millisekunden haben. In beiden Situationen erhalten Sie 0 ms, nachdem Sie es durch 1000000 geteilt haben.

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (p.FirstName + " " + p.LastName);
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();
s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", p.FirstName, p.LastName);
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();


Console.Clear();
Console.WriteLine(n.ToString()+" x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Thread.Sleep(4000);

Das sind meine Ergebnisse:

1000000 x result = string.Format ("{0} {1}", p.FirstName, p.LastName); dauerte: 618ms - 2213706 Ticks
1000000 x result = (p.FirstName + "" + p.LastName); dauerte: 166ms - 595610 Zecken


1
Sehr interessant. Ich habe einen Durchschnitt von 224 ms gegenüber 48 ms, eine Verbesserung von x4,66, sogar besser als Ihre x3,72. Ich frage mich, ob es ein Nachkompilierungswerkzeug gibt, das die IL neu schreiben kann string.Format, das keine zusammengesetzten Formatierungsfunktionen verwendet (dh einfach {0}) und diese durch die erheblich schnellere Verkettung von Zeichenfolgen ersetzt. Ich frage mich, ob eine solche Leistung mit einem vorhandenen IL-Umschreiber wie PostSharp erreichbar ist.
Allon Guralnek

31
Zeichenfolgen sind unveränderlich. Dies bedeutet, dass in Ihrem Code immer wieder dasselbe kleine Stück Speicher verwendet wird. Das Hinzufügen derselben zwei Zeichenfolgen und das wiederholte Erstellen derselben neuen Zeichenfolge wirkt sich nicht auf den Speicher aus. .Net ist intelligent genug, um nur dieselbe Speicherreferenz zu verwenden. Daher testet Ihr Code den Unterschied zwischen den beiden Concat-Methoden nicht wirklich. Siehe Code in meiner Antwort unten.
Ludington

1
Ehrlich gesagt, ich verkette immer, da es für mich einfacher zu lesen ist und wow, es ist schneller :)
puretppc

Geschwindigkeit ist also der einzige Grund, einen über den anderen zu wählen?
Niico

158

Ich bin erstaunt, dass so viele Leute sofort den Code finden möchten, der am schnellsten ausgeführt wird. Wenn die Verarbeitung von EINER MILLION Iterationen NOCH weniger als eine Sekunde dauert, wird dies für den Endbenutzer in irgendeiner Weise erkennbar sein? Nicht sehr wahrscheinlich.

Vorzeitige Optimierung = FAIL.

Ich würde mich für diese String.FormatOption entscheiden, nur weil sie aus architektonischer Sicht am sinnvollsten ist. Die Leistung ist mir erst wichtig, wenn sie zu einem Problem wird (und wenn ja, würde ich mich fragen: Muss ich eine Million Namen gleichzeitig verketten? Sicherlich passen nicht alle auf den Bildschirm ...)

Überlegen Sie, ob Ihr Kunde es später ändern möchte, damit er konfigurieren kann, ob "Firstname Lastname"oder "Lastname, Firstname."mit der Option Format angezeigt werden soll. Dies ist einfach - tauschen Sie einfach die Formatzeichenfolge aus. Mit dem Concat benötigen Sie zusätzlichen Code. Sicher, das klingt in diesem Beispiel nicht nach einer großen Sache, sondern extrapoliert.


47
Guter Punkt in Bezug auf "Vorzeitige Optimierung == FAIL", ja. Wenn Sie jedoch anfangen, für den Ausführungs-Footprint zu bezahlen (Cloud und Infrastruktur als Service, irgendjemand?) Und / oder 1 Million Benutzer bei etwas unterstützen, ist die Antwort auf einen einzelnen Benutzer auf eine Anfrage nicht die Frage. Die Kosten für die Bearbeitung einer Anfrage an einen Benutzer sind Kosten für Ihr
Endergebnis

23
Das ist einfach total falsch. In einer Webentwicklungsumgebung ist Ihr Code zur Zeichenfolgengenerierung häufig sowohl in Ihrem Modell als auch in Ihren Ansichten und Controllern enthalten und kann pro Seitenladevorgang zehntausend Mal aufgerufen werden. Die Reduzierung des Zeitaufwands für die Bewertung von Code zur Zeichenfolgengenerierung um 50% könnte ein enormer Gewinn sein.
Benjamin Sussman

2
Eine solche Frage wird nicht nur in der einen Instanz des OP zutreffen. Die Antwort ist die Art von Dingen, an die sich die Leute erinnern können: "Wie soll ich Saiten zusammenbauen?" wie sie ihren gesamten Code schreiben .
Phil Miller

6
@Benjamin: ... in diesem Fall würden Sie ein Profil erstellen und feststellen, dass dies Ihr Engpass ist. Ich würde Geld wetten, dass Sie das nur aus dem Nichts ziehen; Nachdem ich in der Vergangenheit eine Reihe von Webanwendungen geschrieben und profiliert habe, habe ich fast immer festgestellt, dass der Engpass bei den Antwortzeiten (auf der Serverseite) die Datenbankabfragen sind.
BlueRaja - Danny Pflughoeft

2
Dies ist definitiv keine vorzeitige Optimierung. Ziemlich der Irrtum. Die Zeichenfolgenleistung kann Benutzeroberflächen vollständig blockieren, insbesondere in .NET, wenn Sie viel formatieren und Zeichenfolgen erstellen. ubiquity.acm.org/article.cfm?id=1513451
user99999991

54

Oh je - nachdem ich eine der anderen Antworten gelesen hatte, versuchte ich, die Reihenfolge der Operationen umzukehren - also führte ich zuerst die Verkettung durch, dann das String.Format ...

Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 8ms - 30488 ticks
Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 0ms - 182 ticks

Die Reihenfolge der Operationen macht also einen RIESIGEN Unterschied, oder vielmehr ist die allererste Operation IMMER viel langsamer.

Hier sind die Ergebnisse eines Laufs, bei dem Vorgänge mehr als einmal ausgeführt wurden. Ich habe versucht, die Reihenfolge zu ändern, aber die Dinge folgen im Allgemeinen den gleichen Regeln, sobald das erste Ergebnis ignoriert wird:

Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 5ms - 20335 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 156 ticks
Bill Gates
Console.WriteLine(FirstName + " " + LastName); took: 0ms - 122 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 181 ticks
Bill Gates
Console.WriteLine("{0} {1}", FirstName, LastName); took: 0ms - 122 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 142 ticks
Bill Gates
String.Concat(FirstName, " ", LastName); took: 0ms - 117 ticks

Wie Sie sehen können, sind nachfolgende Läufe derselben Methode (ich habe den Code in 3 Methoden umgestaltet) inkrementell schneller. Die schnellste Methode scheint die Console.WriteLine-Methode (String.Concat (...)) zu sein, gefolgt von der normalen Verkettung und den formatierten Vorgängen.

Die anfängliche Verzögerung beim Start ist wahrscheinlich die Initialisierung von Console Stream, da eine Console.Writeline ("Start!") Vor dem ersten Vorgang alle Zeiten wieder in Einklang bringt.


2
Entfernen Sie dann Console.WriteLine vollständig aus Ihren Tests. Es verzerrt die Ergebnisse!
CShark

Ich beginne immer mit einem Wegwerf- oder "Kontroll"
-Szenario,

36

Zeichenfolgen sind unveränderlich. Dies bedeutet, dass in Ihrem Code immer wieder dasselbe kleine Stück Speicher verwendet wird. Das Hinzufügen derselben zwei Zeichenfolgen und das wiederholte Erstellen derselben neuen Zeichenfolge wirkt sich nicht auf den Speicher aus. .Net ist intelligent genug, um nur dieselbe Speicherreferenz zu verwenden. Daher testet Ihr Code den Unterschied zwischen den beiden Concat-Methoden nicht wirklich.

Probieren Sie dies für die Größe an:

Stopwatch s = new Stopwatch();

int n = 1000000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0, sbElapsedMilliseconds = 0, sbElapsedTicks = 0;

Random random = new Random(DateTime.Now.Millisecond);

string result;
s.Start();
for (var i = 0; i < n; i++)
    result = (random.Next().ToString() + " " + random.Next().ToString());
s.Stop();
cElapsedMilliseconds = s.ElapsedMilliseconds;
cElapsedTicks = s.ElapsedTicks;
s.Reset();

s.Start();
for (var i = 0; i < n; i++)
    result = string.Format("{0} {1}", random.Next().ToString(), random.Next().ToString());
s.Stop();
fElapsedMilliseconds = s.ElapsedMilliseconds;
fElapsedTicks = s.ElapsedTicks;
s.Reset();

StringBuilder sb = new StringBuilder();
s.Start();
for(var i = 0; i < n; i++){
    sb.Clear();
    sb.Append(random.Next().ToString());
    sb.Append(" ");
    sb.Append(random.Next().ToString());
    result = sb.ToString();
}
s.Stop();
sbElapsedMilliseconds = s.ElapsedMilliseconds;
sbElapsedTicks = s.ElapsedTicks;
s.Reset();

Console.WriteLine(n.ToString() + " x result = string.Format(\"{0} {1}\", p.FirstName, p.LastName); took: " + (fElapsedMilliseconds) + "ms - " + (fElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x result = (p.FirstName + \" \" + p.LastName); took: " + (cElapsedMilliseconds) + "ms - " + (cElapsedTicks) + " ticks");
Console.WriteLine(n.ToString() + " x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(\" \"); sb.Append(random.Next().ToString()); result = sb.ToString(); took: " + (sbElapsedMilliseconds) + "ms - " + (sbElapsedTicks) + " ticks");
Console.WriteLine("****************");
Console.WriteLine("Press Enter to Quit");
Console.ReadLine();

Beispielausgabe:

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 513ms - 1499816 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 393ms - 1150148 ticks
1000000 x sb.Clear();sb.Append(random.Next().ToString()); sb.Append(" "); sb.Append(random.Next().ToString()); result = sb.ToString(); took: 405ms - 1185816 ticks

1
Ein StringBuilder und eine Beispielausgabe wurden zur Antwort
hinzugefügt

Ich sehe, wie die Verwendung string.Formatden winzigen Leistungseinbruch hier wert ist. Architektonisch ist es besser, da Sie das Format einfacher ändern können. Aber Stringbuilder verstehe ich wirklich nicht. Jeder andere Thread hier sagt, dass Sie Stringbuilder verwenden sollten, anstatt Zeichenfolgen zu verketten. Was ist der Vorteil? Ganz klar keine Geschwindigkeit, wie dieser Benchmark beweist.
Roryok

22

Schade um die armen Übersetzer

Wenn Sie wissen, dass Ihre Bewerbung auf Englisch bleibt, speichern Sie die Uhr-Ticks. In vielen Kulturen wird jedoch normalerweise der Nachname Vorname beispielsweise in Adressen angezeigt.

Verwenden string.Format()Sie diese Option, insbesondere wenn Ihre Anwendung jemals irgendwohin gehen soll, wo Englisch nicht die erste Sprache ist.


2
Wie würde string.Format()sich in verschiedenen Kulturen unterschiedlich verhalten? Würde es nicht immer noch den Vor- und Nachnamen drucken? Es scheint, als müssten Sie in beiden Situationen die unterschiedliche Kultur berücksichtigen. Ich habe das Gefühl, dass mir hier etwas fehlt.
Broots Waymb

2
Ich stimme @DangerZone zu. Woher string.Format()wissen Sie, dass Sie einen Namen für eine Adresse verwenden? Wenn ich aufgrund der Kultur string.Format()getauscht {0} {1}würde, würde ich es als kaputt betrachten.
Alex McMillan

2
Ich glaube, der Punkt, den Jeremy anstrebte, ist, dass es in dem beschriebenen Szenario zur Unterstützung verschiedener Länder angebracht sein kann, die Formatzeichenfolge selbst in eine Sprachressource zu extrahieren. Für die meisten Länder wäre diese Zeichenfolge "{0} {1}", aber für die Länder, in denen der Vorname der typische Vorgang ist (z. B. Ungarn, Hongkong, Kambodscha, China, Japan, Korea, Madagaskar, Taiwan, Vietnam und Teile Indiens) wäre diese Zeichenfolge stattdessen "{1} {0}".
Richard J Foster

Tatsächlich. Oder fügen Sie subtiler die Formatzeichenfolge als Attribut der Person hinzu. Ich möchte zum Beispiel meinen Nachnamen nach meinem Vornamen haben, mein Kollege Beng jedoch nicht.
Jeremy McGee

14

Hier sind meine Ergebnisse über 100.000 Iterationen:

Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took (avg): 0ms - 689 ticks
Console.WriteLine(p.FirstName + " " + p.LastName); took (avg): 0ms - 683 ticks

Und hier ist der Bankcode:

Stopwatch s = new Stopwatch();

var p = new { FirstName = "Bill", LastName = "Gates" };

//First print to remove the initial cost
Console.WriteLine(p.FirstName + " " + p.LastName);
Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

int n = 100000;
long fElapsedMilliseconds = 0, fElapsedTicks = 0, cElapsedMilliseconds = 0, cElapsedTicks = 0;

for (var i = 0; i < n; i++)
{
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();
    cElapsedMilliseconds += s.ElapsedMilliseconds;
    cElapsedTicks += s.ElapsedTicks;
    s.Reset();
    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    fElapsedMilliseconds += s.ElapsedMilliseconds;
    fElapsedTicks += s.ElapsedTicks;
    s.Reset();
}

Console.Clear();

Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took (avg): " + (fElapsedMilliseconds / n) + "ms - " + (fElapsedTicks / n) + " ticks");
Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took (avg): " + (cElapsedMilliseconds / n) + "ms - " + (cElapsedTicks / n) + " ticks");

Also, ich weiß nicht, wessen Antwort als Antwort zu markieren ist :)


Warum ist der Hintergrund für diese Antwort blau?
user88637

@yossi es ist blau, weil der Antwortende der gleiche ist wie der Fragesteller
Davy8

9

Das Verketten von Zeichenfolgen ist in einem einfachen Szenario wie diesem in Ordnung - es ist komplizierter mit etwas Komplizierterem als diesem, sogar Nachname, Vorname. Mit dem Format können Sie auf einen Blick sehen, wie die endgültige Struktur der Zeichenfolge beim Lesen des Codes aussehen wird. Bei Verkettung ist es fast unmöglich, das Endergebnis sofort zu erkennen (außer bei einem sehr einfachen Beispiel wie diesem).

Auf lange Sicht bedeutet dies, dass Sie, wenn Sie zurückkommen, um Änderungen an Ihrem Zeichenfolgenformat vorzunehmen, entweder die Möglichkeit haben, die Formatzeichenfolge zu ändern oder ein paar Anpassungen vorzunehmen, oder die Stirn runzeln und sich bewegen können Arten von Eigenschaftszugängern, die mit Text gemischt sind, was eher zu Problemen führt.

Wenn Sie .NET 3.5 verwenden, können Sie eine Erweiterungsmethode wie diese verwenden und einen einfachen Ablauf erzielen, der von der Manschettensyntax wie folgt abweicht:

string str = "{0} {1} is my friend. {3}, {2} is my boss.".FormatWith(prop1,prop2,prop3,prop4);

Wenn Ihre Anwendung immer komplexer wird, können Sie entscheiden, dass Sie Zeichenfolgen in Ihrer Anwendung in eine Ressourcendatei verschieben möchten, um sie zu lokalisieren, oder einfach in einen statischen Helfer, um sie ordnungsgemäß zu verwalten. Dies ist VIEL einfacher zu erreichen, wenn Sie konsequent Formate verwendet haben, und Ihr Code kann ganz einfach überarbeitet werden, um so etwas zu verwenden

string name = String.Format(ApplicationStrings.General.InformalUserNameFormat,this.FirstName,this.LastName);

7

Für eine sehr einfache Manipulation würde ich die Verkettung verwenden, aber sobald Sie über 2 oder 3 Elemente hinaus sind, wird das Format IMO geeigneter.

Ein weiterer Grund, String.Format zu bevorzugen, besteht darin, dass .NET-Zeichenfolgen unveränderlich sind und auf diese Weise weniger temporäre / Zwischenkopien erstellt werden.


6

Während ich die Stilpräferenz vollständig verstehe und die Verkettung für meine erste Antwort teilweise basierend auf meiner eigenen Präferenz ausgewählt habe, basierte ein Teil meiner Entscheidung auf dem Gedanken, dass die Verkettung schneller sein würde. Aus Neugier habe ich es getestet und die Ergebnisse waren atemberaubend, besonders für eine so kleine Saite.

Verwenden Sie den folgenden Code:

    System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();

    var p = new { FirstName = "Bill", LastName = "Gates" };

    s.Start();
    Console.WriteLine("{0} {1}", p.FirstName, p.LastName);
    s.Stop();
    Console.WriteLine("Console.WriteLine(\"{0} {1}\", p.FirstName, p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

    s.Reset();
    s.Start();
    Console.WriteLine(p.FirstName + " " + p.LastName);
    s.Stop();

    Console.WriteLine("Console.WriteLine(p.FirstName + \" \" + p.LastName); took: " + s.ElapsedMilliseconds + "ms - " + s.ElapsedTicks + " ticks");

Ich habe folgende Ergebnisse erhalten:

Bill Gates
Console.WriteLine("{0} {1}", p.FirstName, p.LastName); took: 2ms - 7280 ticks
Bill Gates
Console.WriteLine(p.FirstName + " " + p.LastName); took: 0ms - 67 ticks

Die Formatierungsmethode ist über 100-mal langsamer !! Die Verkettung wurde nicht einmal als 1 ms registriert, weshalb ich auch die Timer-Ticks ausgab.


2
Aber natürlich sollten Sie die Operation mehrmals ausführen, um Messungen zu erhalten.
Erikkallen

2
Und den Aufruf von Console.Writeline () verlieren, da dies den Rahmen der Frage sprengt?
Aidanapword

Hast du mit einem Stringbuilder getestet? ;)
Niico

6

Ab C # 6.0 können interpolierte Zeichenfolgen verwendet werden, was das Format noch weiter vereinfacht.

var name = "Bill";
var surname = "Gates";
MessageBox.Show($"Welcome to the show, {name} {surname}!");

Ein interpolierter Zeichenfolgenausdruck sieht aus wie eine Vorlagenzeichenfolge, die Ausdrücke enthält. Ein interpolierter Zeichenfolgenausdruck erstellt eine Zeichenfolge, indem die enthaltenen Ausdrücke durch die ToString-Repräsentationen der Ergebnisse der Ausdrücke ersetzt werden.

Interpolierte Zeichenfolgen weisen eine ähnliche Leistung wie String.Format auf, verbessern jedoch die Lesbarkeit und die kürzere Syntax, da Werte und Ausdrücke in Zeile eingefügt werden.

Bitte lesen Sie auch diesen dotnetperls-Artikel zur String-Interpolation.

Wenn Sie nach einer Standardmethode zum Formatieren Ihrer Zeichenfolgen suchen, ist dies in Bezug auf Lesbarkeit und Leistung sinnvoll (außer wenn Mikrosekunden in Ihrem speziellen Anwendungsfall einen Unterschied machen).


5

Für die grundlegende Verkettung von Zeichenfolgen verwende ich im Allgemeinen den zweiten Stil - einfacher zu lesen und einfacher. Wenn ich jedoch eine kompliziertere String-Kombination mache, entscheide ich mich normalerweise für String.Format.

String.Format spart viele Anführungszeichen und Pluspunkte ...

Console.WriteLine("User {0} accessed {1} on {2}.", user.Name, fileName, timestamp);
vs
Console.WriteLine("User " + user.Name + " accessed " + fileName + " on " + timestamp + ".");

Nur ein paar Zeichen gespeichert, aber ich denke, in diesem Beispiel macht das Format es viel sauberer.


5

Ein besserer Test wäre, Ihren Speicher mit Perfmon und den CLR-Speicherzählern zu überwachen. Meines Wissens nach ist der ganze Grund, warum Sie String.Format verwenden möchten, anstatt nur Strings zu verketten, dass Sie den Garbage Collector unnötig mit temporären Strings belasten, die im nächsten Durchgang zurückgefordert werden müssen, da Strings unveränderlich sind.

StringBuilder und String.Format sind zwar möglicherweise langsamer, aber speichereffizienter.

Was ist so schlimm an der Verkettung von Zeichenfolgen?


Genau; Jede Zeichenfolgenoperation erstellt eine neue Kopie der Zeichenfolge. Der gesamte Speicher wird früher oder später vom Garbage Collector zurückgefordert. Wenn Sie also viele Zeichenfolgen zuweisen, werden Sie möglicherweise später wieder gebissen.
Marnix van Valen

5

Im Allgemeinen bevorzuge ich Ersteres, da es viel einfacher zu lesen ist, wenn die Saiten lang werden.

Der andere Vorteil ist meiner Meinung nach die Leistung, da letztere tatsächlich zwei Anweisungen zum Erstellen von Zeichenfolgen ausführt, bevor die endgültige Zeichenfolge an die Console.Write-Methode übergeben wird. String.Format verwendet meines Erachtens einen StringBuilder unter dem Deckmantel, sodass mehrfache Verkettungen vermieden werden.

Es sollte jedoch beachtet werden, dass die Parameter, die Sie an String.Format übergeben (und andere Methoden wie Console.Write), Werttypen sind, die vor der Übergabe eingerahmt werden, was zu eigenen Leistungstreffern führen kann. Blogbeitrag dazu hier .


1
Dieser Blog-Beitrag ist jetzt unter: jeffbarnes.net/blog/post/2006/08/08/… . Ich leide unter unzureichenden Wiederholungen zum Bearbeiten.
Richard Slater

5

In einer Woche, dem 19. August 2015, wird diese Frage genau sieben (7) Jahre alt sein. Es gibt jetzt einen besseren Weg, dies zu tun. Besser in Bezug auf die Wartbarkeit, da ich keinen Leistungstest durchgeführt habe, als nur Zeichenfolgen zu verketten (aber spielt es heutzutage eine Rolle? Ein paar Millisekunden Unterschied?). Die neue Methode mit C # 6.0 :

var p = new { FirstName = "Bill", LastName = "Gates" };
var fullname = $"{p.FirstName} {p.LastName}";

Diese neue Funktion ist besser , IMO und in unserem Fall sogar besser, da wir Codes haben, in denen wir Querystringe erstellen, deren Werte von einigen Faktoren abhängen. Stellen Sie sich einen Querystring vor, bei dem wir 6 Argumente haben. Also anstatt zum Beispiel:

var qs = string.Format("q1={0}&q2={1}&q3={2}&q4={3}&q5={4}&q6={5}", 
    someVar, anotherVarWithLongName, var3, var4, var5, var6)

in kann so geschrieben werden und ist leichter zu lesen:

var qs=$"q1={someVar}&q2={anotherVarWithLongName}&q3={var3}&q4={var4}&q5={var5}&q6={var6}";

In der Tat ist der neue Weg von C # 6.0 besser als frühere Alternativen - zumindest unter dem Gesichtspunkt der Lesbarkeit.
Philippe

Das stimmt. Und es ist auch sicherer, da Sie sich keine Gedanken darüber machen müssen, welches Objekt zu welchem ​​Index (Platzhalter) gehört, da Sie die Objekte direkt dort platzieren, wo Sie es haben möchten.
von v.

Übrigens ruft es tatsächlich Format auf (zumindest bei Roslyn).
Philippe

Übrigens, worauf sich dieses Poster bezieht, heißt "String-Interpolation" und wird an anderer Stelle in diesem Thread angesprochen.
CShark

4
  1. Die Formatierung ist die ".NET" -Methode. Bestimmte Refactoring-Tools (zum Beispiel Refactor!) Schlagen sogar vor, den Code im Concat-Stil zu refactorisieren, um den Formatierungsstil zu verwenden.
  2. Die Formatierung ist für den Compiler einfacher zu optimieren (obwohl die zweite wahrscheinlich überarbeitet wird, um die schnelle Concat-Methode zu verwenden).
  3. Die Formatierung ist normalerweise klarer zu lesen (insbesondere bei „ausgefallenen“ Formatierungen).
  4. Formatierung bedeutet implizite Aufrufe von '.ToString' für alle Variablen, was die Lesbarkeit verbessert.
  5. Laut "Effective C #" sind die .NET-Implementierungen "WriteLine" und "Format" durcheinander, sie autoboxen alle Werttypen (was schlecht ist). "Effective C #" empfiehlt, ".ToString" -Aufrufe explizit durchzuführen, was meiner Meinung nach falsch ist (siehe Jeffs Beitrag ).
  6. Derzeit werden Formatierungshinweise vom Compiler nicht überprüft, was zu Laufzeitfehlern führt. Dies könnte jedoch in zukünftigen Versionen geändert werden.

4

Ich würde die Zeichenfolge String.Format verwenden, aber ich hätte auch die Formatzeichenfolge in den Ressourcendateien, damit sie für andere Sprachen lokalisiert werden kann. Mit einem einfachen String-Concat können Sie das nicht tun. Wenn Sie diese Zeichenfolge nie lokalisieren müssen, ist dies natürlich kein Grund zum Nachdenken. Es kommt wirklich darauf an, wofür der String ist.

Wenn es dem Benutzer angezeigt wird, würde ich String.Format verwenden, damit ich es lokalisieren kann, wenn ich muss - und FxCop wird es für mich überprüfen, nur für den Fall :)

Wenn es Zahlen oder andere Dinge enthält, die keine Zeichenfolgen sind (z. B. Datumsangaben), würde ich String.Format verwenden, da ich dadurch mehr Kontrolle über die Formatierung habe .

Wenn es darum geht, eine Abfrage wie SQL zu erstellen , würde ich Linq verwenden .

Wenn Sie Zeichenfolgen innerhalb einer Schleife verketten möchten, würde ich StringBuilder verwenden , um Leistungsprobleme zu vermeiden.

Wenn es sich um eine Ausgabe handelt, die der Benutzer nicht sieht und die Leistung nicht beeinträchtigt, würde ich String.Format verwenden, da ich es sowieso gewohnt bin und es nur gewohnt bin :)


3

Wenn Sie mit etwas zu tun haben, das einfach zu lesen sein muss (und dies ist der meiste Code), würde ich mich an die Operator-Überlastungsversion halten, AUSSER:

  • Der Code muss millionenfach ausgeführt werden
  • Du machst Tonnen von Concats (mehr als 4 ist eine Tonne)
  • Der Code ist auf das Compact Framework ausgerichtet

Unter mindestens zwei dieser Umstände würde ich stattdessen StringBuilder verwenden.


3

Ich wähle basierend auf der Lesbarkeit. Ich bevorzuge die Formatierungsoption, wenn die Variablen Text enthalten. In diesem Beispiel:

Console.WriteLine("User {0} accessed {1} on {2}.", 
                   user.Name, fileName, timestamp);

Sie verstehen die Bedeutung auch ohne Variablennamen, während der Concat mit Anführungszeichen und + Zeichen überfüllt ist und meine Augen verwirrt:

Console.WriteLine("User " + user.Name + " accessed " + fileName + 
                  " on " + timestamp + ".");

(Ich habe Mikes Beispiel ausgeliehen, weil es mir gefällt)

Wenn die Formatzeichenfolge ohne Variablennamen nicht viel bedeutet, muss ich concat verwenden:

   Console.WriteLine("{0} {1}", p.FirstName, p.LastName);

Mit der Formatoption kann ich die Variablennamen lesen und sie den entsprechenden Nummern zuordnen. Die concat-Option erfordert das nicht. Ich bin immer noch verwirrt von den Anführungszeichen und + Zeichen, aber die Alternative ist schlimmer. Rubin?

   Console.WriteLine(p.FirstName + " " + p.LastName);

Performance weise erwarte ich, dass die Formatoption langsamer sein dann die Concat, da Format die Zeichenfolge erfordert werden analysiert . Ich erinnere mich nicht, dass ich diese Art von Unterricht optimieren musste, aber wenn ich das tat, würde ich mir stringMethoden wie Concat()und ansehenJoin() .

Der andere Vorteil des Formats besteht darin, dass die Formatzeichenfolge in eine Konfigurationsdatei eingefügt werden kann. Sehr praktisch mit Fehlermeldungen und UI-Text.


3

Wenn Sie das Ergebnis lokalisieren möchten, ist String.Format unerlässlich, da in verschiedenen natürlichen Sprachen die Daten möglicherweise nicht in derselben Reihenfolge vorliegen.


2

Ich denke, das hängt stark davon ab, wie komplex die Ausgabe ist. Ich neige dazu, das Szenario zu wählen, das zu der Zeit am besten funktioniert.

Wählen Sie das richtige Werkzeug für den Job: D Was am saubersten aussieht!


2

Ich bevorzuge auch die zweite, aber ich habe derzeit keine rationalen Argumente, um diese Position zu unterstützen.


2

Schön!

Gerade hinzugefügt

        s.Start();
        for (var i = 0; i < n; i++)
            result = string.Concat(p.FirstName, " ", p.LastName);
        s.Stop();
        ceElapsedMilliseconds = s.ElapsedMilliseconds;
        ceElapsedTicks = s.ElapsedTicks;
        s.Reset();

Und es ist noch schneller (ich denke string.Concat wird in beiden Beispielen aufgerufen, aber das erste erfordert eine Art Übersetzung).

1000000 x result = string.Format("{0} {1}", p.FirstName, p.LastName); took: 249ms - 3571621 ticks
1000000 x result = (p.FirstName + " " + p.LastName); took: 65ms - 944948 ticks
1000000 x result = string.Concat(p.FirstName, " ", p.LastName); took: 54ms - 780524 ticks

2
Es dauert genau genauso lange, da vom Operator auf Operatoren basierende Zeichenfolgenverkettungen in Aufrufe übersetzt werden string.Concat(...). Es wird während der Kompilierung durchgeführt, sodass es keinen Einfluss auf die Laufzeitleistung hat. Wenn Sie Ihre Tests mehrmals ausführen oder sie an größeren Testmustern ausführen, werden Sie feststellen, dass sie identisch sind.
Allon Guralnek

2

Da ich nicht denke, dass die Antworten hier alles abdecken, möchte ich hier eine kleine Ergänzung machen.

Console.WriteLine(string format, params object[] pars)Anrufe string.Format. Das '+' impliziert eine Verkettung von Zeichenfolgen. Ich denke nicht, dass dies immer mit Stil zu tun hat. Ich neige dazu, die beiden Stile zu mischen, je nachdem, in welchem ​​Kontext ich mich befinde.

Kurze Antwort

Die Entscheidung, vor der Sie stehen, hängt mit der Zuweisung von Zeichenfolgen zusammen. Ich werde versuchen, es einfach zu machen.

Sagen Sie, Sie haben

string s = a + "foo" + b;

Wenn Sie dies ausführen, wird Folgendes ausgewertet:

string tmp1 = a;
string tmp2 = "foo" 
string tmp3 = concat(tmp1, tmp2);
string tmp4 = b;
string s = concat(tmp3, tmp4);

tmpHier handelt es sich nicht wirklich um eine lokale Variable, sondern um eine temporäre Variable für die JIT (sie wird auf den IL-Stack übertragen). Wenn Sie eine Zeichenfolge auf den Stapel schieben (zldstr in IL für Literale), setzen Sie einen Verweis auf einen Zeichenfolgenzeiger auf dem Stapel.

In dem Moment, in dem Sie anrufen concat diese Referenz wird zu einem Problem, da keine Zeichenfolgenreferenz verfügbar ist, die beide Zeichenfolgen enthält. Dies bedeutet, dass .NET einen neuen Speicherblock zuweisen und ihn dann mit den beiden Zeichenfolgen füllen muss. Der Grund dafür ist, dass die Zuweisung relativ teuer ist.

Was die Frage ändert zu: Wie können Sie die Anzahl der reduzieren concat Operationen ?

Die grobe Antwort lautet also: string.FormatFür> 1 Concat funktioniert '+' für 1 Concat einwandfrei. Und wenn Sie sich nicht für die Optimierung der Mikroleistung interessieren, string.Formatfunktioniert dies im allgemeinen Fall einwandfrei.

Ein Hinweis zur Kultur

Und dann gibt es so etwas wie Kultur ...

string.Formatermöglicht es Ihnen, CultureInfoin Ihrer Formatierung zu verwenden. Ein einfacher Operator '+' verwendet die aktuelle Kultur.

Dies ist besonders wichtig, wenn Sie Dateiformate und f.ex schreiben. doubleWerte, die Sie einer Zeichenfolge hinzufügen. Auf verschiedenen Computern werden möglicherweise unterschiedliche Zeichenfolgen angezeigt, wenn Sie keine string.Formatexplizite Zeichenfolge verwenden CultureInfo.

F.ex. Überlegen Sie, was passiert, wenn Sie ein '.' ändern. Für ein ',' beim Schreiben Ihrer Datei mit durch Kommas getrennten Werten ... auf Niederländisch ist das Dezimaltrennzeichen ein Komma, sodass Ihr Benutzer möglicherweise nur eine 'lustige' Überraschung erhält.

Weitere detaillierte Antwort

Wenn Sie die genaue Größe der Zeichenfolge vorher nicht kennen, verwenden Sie am besten eine solche Richtlinie, um die von Ihnen verwendeten Puffer zu ordnen. Der Leerraum wird zuerst gefüllt, wonach die Daten kopiert werden.

Wachsen bedeutet, einen neuen Speicherblock zuzuweisen und die alten Daten in den neuen Puffer zu kopieren. Der alte Speicherblock kann dann freigegeben werden. An diesem Punkt erhalten Sie das Endergebnis: Wachsen ist eine teure Operation.

Der praktischste Weg, dies zu tun, ist die Verwendung einer Gesamtzuordnungsrichtlinie. Die gebräuchlichste Richtlinie besteht darin, Puffer mit einer Potenz von 2 zuzuweisen. Natürlich müssen Sie dies etwas intelligenter tun (da es keinen Sinn macht, von 1,2,4,8 zu wachsen, wenn Sie bereits wissen, dass Sie 128 Zeichen benötigen ) aber du bekommst das Bild. Die Richtlinie stellt sicher, dass Sie nicht zu viele der oben beschriebenen teuren Vorgänge benötigen.

StringBuilderist eine Klasse, die den zugrunde liegenden Puffer grundsätzlich in Zweierpotenzen zusammenfasst. string.Formatverwendet StringBuilderunter der Haube.

Dies macht Ihre Entscheidung zu einem grundlegenden Kompromiss zwischen Gesamtzuweisung und Anhängen (-mehrfach) (ohne Kultur) oder einfach Zuweisung und Anhängen.


1

Persönlich ist die zweite, da alles, was Sie verwenden, in der direkten Reihenfolge, in der sie ausgegeben wird. Während Sie bei der ersten die Werte {0} und {1} mit der richtigen Var abgleichen müssen, was leicht zu vermasseln ist.

Zumindest ist es nicht so schlimm wie das C ++ - Sprintf, bei dem das Ganze explodiert, wenn der Variablentyp falsch ist.

Da die zweite inline ist und nicht nach allen {0} Dingen gesucht und ersetzt werden muss, sollte letztere schneller sein ... obwohl ich es nicht genau weiß.


1

Eigentlich mag ich die erste, weil es mir einfacher erscheint, wenn viele Variablen mit dem Text vermischt sind. Außerdem ist es einfacher, mit Anführungszeichen umzugehen, wenn Sie das Format string.Format () verwenden. Hier ist eine anständige Analyse der String-Verkettung.


1

Ich bin immer die string.Format () -Route gegangen. Die Möglichkeit, Formate in Variablen wie Nathans Beispiel zu speichern, ist ein großer Vorteil. In einigen Fällen kann ich eine Variable anhängen, aber sobald mehr als eine Variable verkettet ist, überarbeite ich die Formatierung.


1

Oh, und der Vollständigkeit halber ist das Folgende ein paar Ticks schneller als die normale Verkettung:

Console.WriteLine(String.Concat(p.FirstName," ",p.LastName));

1

Das erste (Format) sieht für mich besser aus. Es ist besser lesbar und Sie erstellen keine zusätzlichen temporären Zeichenfolgenobjekte.


1

Ich war neugierig, wo StringBuilder mit diesen Tests stand. Ergebnisse unten ...

class Program {
   static void Main(string[] args) {

      var p = new { FirstName = "Bill", LastName = "Gates" };

      var tests = new[] {
         new { Name = "Concat", Action = new Action(delegate() { string x = p.FirstName + " " + p.LastName; }) },
         new { Name = "Format", Action = new Action(delegate() { string x = string.Format("{0} {1}", p.FirstName, p.LastName); }) },
         new { Name = "StringBuilder", Action = new Action(delegate() {
            StringBuilder sb = new StringBuilder();
            sb.Append(p.FirstName);
            sb.Append(" ");
            sb.Append(p.LastName);
            string x = sb.ToString();
         }) }
      };

      var Watch = new Stopwatch();
      foreach (var t in tests) {
         for (int i = 0; i < 5; i++) {
            Watch.Reset();
            long Elapsed = ElapsedTicks(t.Action, Watch, 10000);
            Console.WriteLine(string.Format("{0}: {1} ticks", t.Name, Elapsed.ToString()));
         }
      }
   }

   public static long ElapsedTicks(Action ActionDelg, Stopwatch Watch, int Iterations) {
      Watch.Start();
      for (int i = 0; i < Iterations; i++) {
         ActionDelg();
      }
      Watch.Stop();
      return Watch.ElapsedTicks / Iterations;
   }
}

Ergebnisse:

Concat: 406 Zecken
Concat: 356 Zecken
Concat: 411 Zecken
Concat: 299 Zecken
Concat: 266 Zecken
Format: 5269 Ticks
Format: 954 Ticks
Format: 1004 Ticks
Format: 984 Ticks
Format: 974 Ticks
StringBuilder: 629 Ticks
StringBuilder: 484 Ticks
StringBuilder: 482 Ticks
StringBuilder: 508 Ticks
StringBuilder: 504 Ticks

1

Laut dem MCSD-Vorbereitungsmaterial schlägt Microsoft vor, den Operator + zu verwenden, wenn eine sehr kleine Anzahl von Verkettungen (wahrscheinlich 2 bis 4) behandelt wird. Ich bin mir immer noch nicht sicher warum, aber es ist etwas zu beachten.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.