Sortie de chaîne: format ou concat en C #?


178

Supposons que vous souhaitiez générer ou concater des chaînes. Lequel des styles suivants préférez-vous?

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

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

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

Utilisez-vous plutôt le format ou concattez-vous simplement des chaînes? Quel est ton préféré? L'un de ceux-ci vous fait-il mal aux yeux?

Avez-vous des arguments rationnels pour utiliser l'un et pas l'autre?

J'irais pour le deuxième.

Réponses:


88

Essayez ce code.

C'est une version légèrement modifiée de votre code.
1. J'ai supprimé Console.WriteLine car il est probablement quelques ordres de grandeur plus lent que ce que j'essaie de mesurer.
2. Je démarre le chronomètre avant la boucle et je l'arrête juste après, de cette façon je ne perds pas de précision si la fonction prend par exemple 26,4 ticks pour s'exécuter.
3. La façon dont vous avez divisé le résultat par quelques itérations était erronée. Voyez ce qui se passe si vous avez 1000 millisecondes et 100 millisecondes. Dans les deux cas, vous obtiendrez 0 ms après l'avoir divisé par 1000000.

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);

Voici mes résultats:

1000000 x résultat = string.Format ("{0} {1}", p.FirstName, p.LastName); pris: 618ms - 2213706 ticks
1000000 x résultat = (p.Prénom + "" + p.LastName); a pris: 166ms - 595610 tiques


1
Très intéressant. J'ai eu une moyenne de 224 ms contre 48 ms, une amélioration de x4,66, encore meilleure que votre x3,72. Je me demande s'il existe un outil de post-compilation qui peut réécrire l'IL de string.Formatqui n'utilise aucune fonctionnalité de formatage composite (c'est-à-dire simplement simple {0}) et les remplace par la concaténation de chaînes considérablement plus rapide. Je me demande qu'un tel exploit est réalisable avec un réécriveur IL existant tel que PostSharp.
Allon Guralnek

31
Les chaînes sont immuables, cela signifie que la même petite mémoire est utilisée à plusieurs reprises dans votre code. Ajouter les deux mêmes chaînes ensemble et créer la même nouvelle chaîne encore et encore n'a pas d'impact sur la mémoire. .Net est suffisamment intelligent pour utiliser la même référence mémoire. Par conséquent, votre code ne teste pas vraiment la différence entre les deux méthodes concat. Voir le code dans ma réponse ci-dessous.
Ludington

1
Honnêtement, je concatène toujours car c'est plus facile à lire pour moi et wow c'est plus rapide :)
puretppc

La vitesse est donc la seule raison de choisir l'un plutôt que l'autre?
niico

158

Je suis étonné que tant de gens veuillent immédiatement trouver le code qui s'exécute le plus rapidement. Si UN MILLION d'itérations prennent ENCORE moins d'une seconde à traiter, cela sera-t-il perceptible de quelque manière que ce soit pour l'utilisateur final? Probablement pas.

Optimisation prématurée = FAIL.

J'irais avec l' String.Formatoption, uniquement parce que cela a le plus de sens d'un point de vue architectural. Je me fiche de la performance jusqu'à ce que cela devienne un problème (et si c'était le cas, je me demanderais: Dois-je concaténer un million de noms à la fois? Ils ne rentreront certainement pas tous à l'écran ...)

Considérez si votre client souhaite le modifier ultérieurement afin qu'il puisse configurer l'affichage "Firstname Lastname"ou "Lastname, Firstname."l'option Avec l'option Format, c'est facile - il suffit de remplacer la chaîne de format. Avec le concat, vous aurez besoin de code supplémentaire. Bien sûr, cela ne semble pas grave dans cet exemple particulier, mais extrapolez.


47
Bon point en termes de "Optimisation Prématurée == ÉCHEC", oui. Mais, lorsque vous commencez à payer pour l'empreinte d'exécution (cloud et infrastructure en tant que service, n'importe qui?) Et / ou que vous commencez à prendre en charge 1 million d'utilisateurs sur quelque chose, la réponse à un seul utilisateur à une demande n'est pas la question. Le coût du traitement d'une demande adressée à un utilisateur est un coût pour votre résultat
net

23
C'est tout simplement faux. Dans un environnement de développement Web, votre code de génération de chaîne sera souvent profondément ancré dans votre modèle, vos vues et vos contrôleurs et peut être appelé des dizaines de milliers de fois par chargement de page. Réduire de 50% le temps passé à évaluer le code de génération de chaînes pourrait être une énorme victoire.
Benjamin Sussman

2
Une question comme celle-ci ne s'appliquera pas seulement dans le seul cas du PO. La réponse est le genre de chose dont les gens peuvent se souvenir comme "de quelle manière dois-je assembler des cordes?" comme ils écrivent tout leur code.
Phil Miller

6
@Benjamin: ... dans ce cas, vous profileriez et trouveriez que c'est votre goulot d'étranglement. Je parierais que vous tirez cela de nulle part, cependant; ayant écrit et profilé un certain nombre d'applications Web dans le passé, j'ai presque toujours trouvé que le goulot d'étranglement dans les temps de réponse (côté serveur) était les requêtes de base de données.
BlueRaja - Danny Pflughoeft

2
Ce n'est certainement PAS une optimisation prématurée. Tout à fait l'erreur. Les performances des chaînes peuvent complètement bloquer les interfaces utilisateur, en particulier dans .NET si vous effectuez beaucoup de mise en forme et de création de chaînes. ubiquity.acm.org/article.cfm?id=1513451
user99999991

54

Oh mon Dieu - après avoir lu l'une des autres réponses, j'ai essayé d'inverser l'ordre des opérations - alors effectuez d'abord la concaténation, puis le 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

Ainsi l'ordre des opérations fait une ENORME différence, ou plutôt la toute première opération est TOUJOURS beaucoup plus lente.

Voici les résultats d'une exécution où les opérations sont effectuées plusieurs fois. J'ai essayé de changer les commandes mais les choses suivent généralement les mêmes règles, une fois que le premier résultat est ignoré:

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

Comme vous pouvez le voir, les exécutions ultérieures de la même méthode (j'ai refactoré le code en 3 méthodes) sont progressivement plus rapides. La méthode la plus rapide semble être la méthode Console.WriteLine (String.Concat (...)), suivie de la concaténation normale, puis des opérations formatées.

Le délai initial de démarrage est probablement l'initialisation de Console Stream, car placer une Console.Writeline ("Start!") Avant la première opération ramène tous les temps en ligne.


2
Supprimez ensuite complètement Console.WriteLine de vos tests. Cela déforme les résultats!
CShark

Je commence toujours par un scénario jetable ou «de contrôle» lorsque
j'exécute des

36

Les chaînes sont immuables, cela signifie que le même petit morceau de mémoire est utilisé à plusieurs reprises dans votre code. Ajouter les deux mêmes chaînes ensemble et créer la même nouvelle chaîne encore et encore n'a pas d'impact sur la mémoire. .Net est suffisamment intelligent pour utiliser la même référence mémoire. Par conséquent, votre code ne teste pas vraiment la différence entre les deux méthodes concat.

Essayez ceci pour la taille:

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();

Exemple de sortie:

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
Ajout d'un StringBuilder et d'un exemple de sortie à la réponse
mikeschuld

Je vois à quel point l'utilisation string.Formatvaut le petit coup de performance ici. Sur le plan architectural, c'est mieux car cela signifie que vous pouvez modifier le format plus facilement. Mais je ne vois vraiment pas l'intérêt de stringbuilder. Tous les autres threads ici indiquent que vous devez utiliser Stringbuilder au lieu de concaténer des chaînes. Quel est l'avantage? Clairement pas de vitesse, comme le prouve cette référence.
roryok

22

Pitié les pauvres traducteurs

Si vous savez que votre application restera en anglais, alors très bien, enregistrez les tics d'horloge. Cependant, de nombreuses cultures voient généralement Lastname Firstname dans, par exemple, les adresses.

Alors utilisez string.Format(), surtout si vous allez un jour avoir votre application partout où l'anglais n'est pas la première langue.


2
Comment se string.Format()comporterait-il différemment selon les cultures? N'afficherait-il pas toujours le prénom puis le nom de famille? Il semble que vous deviez prendre en compte la culture différente dans les deux situations. J'ai l'impression de manquer quelque chose ici.
Broots Waymb

2
Je suis d'accord avec @DangerZone .. Comment string.Format()sauriez-vous que vous utilisiez un nom pour une adresse? S'il était string.Format()échangé en {0} {1}fonction de la culture, je le considérerais comme cassé.
Alex McMillan

2
Je crois que le point que Jeremy essayait de faire valoir est que dans le scénario décrit pour prendre en charge différents pays, il peut être approprié d'extraire la chaîne de format elle-même dans une ressource linguistique. Pour la plupart des pays, cette chaîne serait "{0} {1}", mais pour les pays où le nom de famille en premier est l'opération typique (par exemple, Hongrie, Hong Kong, Cambodge, Chine, Japon, Corée, Madagascar, Taïwan, Vietnam et parties de l'Inde), cette chaîne serait "{1} {0}" à la place.
Richard J Foster

En effet. Ou, plus subtilement, ajoutez la chaîne de format comme attribut de la personne. Par exemple, j'aime avoir mon nom de famille après mon prénom, mais pas mon collègue Beng.
Jeremy McGee

14

Voici mes résultats sur 100 000 itérations:

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

Et voici le code de banc:

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");

Donc, je ne sais pas quelle réponse marquer comme réponse :)


Pourquoi le fond est-il bleu pour cette réponse?
user88637

@yossi c'est bleu parce que le répondeur est le même que le demandeur
Davy8

9

La concaténation de chaînes est bien dans un scénario simple comme celui-ci - c'est plus compliqué avec quelque chose de plus compliqué que cela, même LastName, FirstName. Avec le format, vous pouvez voir, en un coup d'œil, quelle sera la structure finale de la chaîne lors de la lecture du code, avec la concaténation, il devient presque impossible de discerner immédiatement le résultat final (sauf avec un exemple très simple comme celui-ci).

Ce que cela signifie à long terme, c'est que lorsque vous reviendrez pour modifier le format de votre chaîne, vous pourrez soit entrer et faire quelques ajustements à la chaîne de format, soit plisser votre front et commencer à vous déplacer. types d’accesseurs de propriété mélangés à du texte, ce qui est plus susceptible d’introduire des problèmes.

Si vous utilisez .NET 3.5, vous pouvez utiliser une méthode d'extension comme celle-ci et obtenir une syntaxe simple et rapide comme celle-ci:

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

Enfin, à mesure que votre application devient de plus en plus complexe, vous pouvez décider que pour gérer correctement les chaînes de votre application, vous souhaitez les déplacer dans un fichier de ressources à localiser ou simplement dans un assistant statique. Ce sera BEAUCOUP plus facile à réaliser si vous avez toujours utilisé des formats, et votre code peut être tout simplement refacturé pour utiliser quelque chose comme

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

7

Pour une manipulation très simple, j'utiliserais la concaténation, mais une fois que vous avez dépassé 2 ou 3 éléments, le format devient plus approprié à l'OMI.

Une autre raison de préférer String.Format est que les chaînes .NET sont immuables et que cela crée moins de copies temporaires / intermédiaires.


6

Bien que je comprenne parfaitement la préférence de style et que j'ai choisi la concaténation pour ma première réponse en partie en fonction de ma propre préférence, une partie de ma décision était basée sur l'idée que la concaténation serait plus rapide. Donc, par curiosité, je l'ai testé et les résultats ont été stupéfiants, surtout pour une si petite corde.

En utilisant le code suivant:

    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");

J'ai obtenu les résultats suivants:

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

L'utilisation de la méthode de formatage est plus de 100 fois plus lente !! La concaténation ne s'est même pas enregistrée à 1 ms, c'est pourquoi j'ai également sorti les graduations de la minuterie.


2
Mais bien sûr, vous devez effectuer l'opération plus d'une fois pour obtenir des mesures.
erikkallen

2
Et perdre l'appel à Console.Writeline () car il dépasse la portée de la question?
Aidanapword

avez-vous testé avec un stringbuilder? ;)
niico

6

À partir de C # 6.0, des chaînes interpolées peuvent être utilisées pour ce faire, ce qui simplifie encore plus le format.

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

Une expression de chaîne interpolée ressemble à une chaîne de modèle contenant des expressions. Une expression de chaîne interpolée crée une chaîne en remplaçant les expressions contenues par les représentations ToString des résultats des expressions.

Les chaînes interpolées ont des performances similaires à String.Format, mais une meilleure lisibilité et une syntaxe plus courte, en raison du fait que les valeurs et les expressions sont insérées en ligne.

Veuillez également vous référer à cet article dotnetperls sur l'interpolation de chaîne.

Si vous recherchez un moyen par défaut de formater vos chaînes, cela a du sens en termes de lisibilité et de performances (sauf si les microsecondes vont faire une différence dans votre cas d'utilisation spécifique).


5

Pour la concaténation de chaînes de base, j'utilise généralement le second style - plus facile à lire et plus simple. Cependant, si je fais une combinaison de chaînes plus compliquée, j'opte généralement pour String.Format.

String.Format enregistre de nombreux guillemets et plus ...

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

Seuls quelques charicteurs ont été enregistrés, mais je pense que, dans cet exemple, le format le rend beaucoup plus propre.


5

Un meilleur test serait de surveiller votre mémoire en utilisant Perfmon et les compteurs de mémoire CLR. Je crois comprendre que la raison pour laquelle vous souhaitez utiliser String.Format au lieu de simplement concaténer des chaînes est que, puisque les chaînes sont immuables, vous chargez inutilement le ramasse-miettes avec des chaînes temporaires qui doivent être récupérées lors de la prochaine passe.

StringBuilder et String.Format, bien que potentiellement plus lents, sont plus efficaces en mémoire.

Qu'y a-t-il de si mauvais dans la concaténation de chaînes?


Je suis d'accord; chaque opération de chaîne crée une nouvelle copie de la chaîne. Toute cette mémoire sera récupérée par le garbage collector tôt ou tard. Donc, allouer beaucoup de cordes peut revenir vous mordre plus tard.
Marnix van Valen

5

En général, je préfère le premier, car surtout lorsque les cordes deviennent longues, cela peut être beaucoup plus facile à lire.

L'autre avantage est, je crois, celui des performances, car ce dernier exécute en fait 2 instructions de création de chaîne avant de passer la chaîne finale à la méthode Console.Write. String.Format utilise un StringBuilder sous les couvertures, je crois, donc les concaténations multiples sont évitées.

Il convient cependant de noter que si les paramètres que vous passez à String.Format (et d'autres méthodes telles que Console.Write) sont des types de valeur, ils seront encadrés avant d'être transmis, ce qui peut fournir ses propres performances. Article de blog à ce sujet ici .


1
Ce billet de blog est maintenant à: jeffbarnes.net/blog/post/2006/08/08/… . Je souffre d'une répétition insuffisante pour modifier.
Richard Slater

5

Dans une semaine le 19 août 2015, cette question aura exactement sept (7) ans. Il existe maintenant une meilleure façon de procéder. Mieux en termes de maintenabilité car je n'ai fait aucun test de performance par rapport à la simple concaténation de chaînes (mais est-ce important de nos jours? Quelques millisecondes de différence?). La nouvelle façon de le faire avec C # 6.0 :

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

Cette nouvelle fonctionnalité est meilleure , IMO, et en fait meilleure dans notre cas car nous avons des codes où nous construisons des chaînes de requêtes dont les valeurs dépendent de certains facteurs. Imaginez une chaîne de requêtes où nous avons 6 arguments. Donc au lieu de faire un, par exemple:

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

in peut être écrit comme ceci et c'est plus facile à lire:

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

En effet, la nouvelle manière de C # 6.0 est meilleure que les alternatives précédentes - du moins du point de vue de la lisibilité.
Philippe

C'est vrai. Et c'est aussi plus sûr car vous n'avez pas à vous soucier de quel objet va à quel index (espace réservé) puisque vous placerez directement les objets là où vous le souhaitez.
von v.

BTW, il appelle en fait Format (au moins avec Roslyn).
Philippe

BTW, ce à quoi cette affiche fait référence est appelé "interpolation de chaîne", et est abordé ailleurs dans ce fil.
CShark

4
  1. Le formatage est la manière «.NET» de le faire. Certains outils de refactoring (Refactor! Pour un) proposeront même de refactoriser le code de style concat pour utiliser le style de mise en forme.
  2. Le formatage est plus facile à optimiser pour le compilateur (bien que le second sera probablement refactorisé pour utiliser la méthode 'Concat' qui est rapide).
  3. Le formatage est généralement plus clair à lire (en particulier avec un formatage «sophistiqué»).
  4. Le formatage signifie des appels implicites à '.ToString' sur toutes les variables, ce qui est bon pour la lisibilité.
  5. Selon «Effective C #», les implémentations .NET 'WriteLine' et 'Format' sont faussées, elles renvoient automatiquement tous les types de valeur (ce qui est mauvais). «Effective C #» conseille d'effectuer explicitement des appels '.ToString', ce qui à mon humble avis est faux (voir la publication de Jeff )
  6. Pour le moment, les indications de type de formatage ne sont pas vérifiées par le compilateur, ce qui entraîne des erreurs d'exécution. Cependant, cela pourrait être modifié dans les versions futures.

4

Je choisis en fonction de la lisibilité. Je préfère l'option de format lorsqu'il y a du texte autour des variables. Dans cet exemple:

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

vous comprenez la signification même sans noms de variables, alors que le concat est encombré de guillemets et de signes + et me trouble les yeux:

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

(J'ai emprunté l'exemple de Mike parce que je l'aime bien)

Si la chaîne de format ne signifie pas grand-chose sans les noms de variables, je dois utiliser concat:

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

L'option de format me fait lire les noms de variables et les mapper aux nombres correspondants. L'option concat ne nécessite pas cela. Je suis toujours confus par les guillemets et les signes +, mais l'alternative est pire. Rubis?

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

En termes de performances, je m'attends à ce que l'option de format soit plus lente que le concat, car le format nécessite que la chaîne soit analysée . Je ne me souviens pas avoir à optimiser ce type d'instruction, mais si je le faisais, je regarderais des stringméthodes comme Concat()et Join().

L'autre avantage du format est que la chaîne de format peut être placée dans un fichier de configuration. Très pratique avec les messages d'erreur et le texte de l'interface utilisateur.


4

J'utiliserais le String.Format, mais j'aurais également la chaîne de format dans les fichiers de ressources afin qu'elle puisse être localisée pour d'autres langues. L'utilisation d'une simple chaîne concat ne vous permet pas de le faire. Évidemment, si vous n'aurez jamais besoin de localiser cette chaîne, ce n'est pas une raison d'y penser. Cela dépend vraiment de ce à quoi sert la chaîne.

Si cela doit être montré à l'utilisateur, j'utiliserais String.Format pour que je puisse localiser si nécessaire - et FxCop le vérifiera pour moi, juste au cas où :)

S'il contient des nombres ou d'autres choses non-string (par exemple des dates), j'utiliserais String.Format car cela me donne plus de contrôle sur la mise en forme .

Si c'est pour créer une requête comme SQL, j'utiliserais Linq .

Si pour concaténer des chaînes dans une boucle, j'utiliserais StringBuilder pour éviter les problèmes de performances.

Si c'est pour une sortie que l'utilisateur ne verra pas et que cela n'affectera pas les performances, j'utiliserais String.Format parce que j'ai l'habitude de l'utiliser de toute façon et que je suis juste habitué :)


3

Si vous avez affaire à quelque chose qui doit être facile à lire (et c'est la plupart du code), je m'en tiendrai à la version de surcharge de l'opérateur SAUF:

  • Le code doit être exécuté des millions de fois
  • Tu fais des tonnes de concats (plus de 4 c'est une tonne)
  • Le code est orienté vers le Compact Framework

Dans au moins deux de ces circonstances, j'utiliserais StringBuilder à la place.


3

Si vous avez l'intention de localiser le résultat, String.Format est essentiel car différents langages naturels peuvent même ne pas avoir les données dans le même ordre.


2

Je pense que cela dépend fortement de la complexité de la sortie. J'ai tendance à choisir le scénario qui fonctionne le mieux à l'époque.

Choisissez le bon outil en fonction du travail: D Celui qui semble le plus propre!


2

Je préfère également le second, mais je n'ai pas d'arguments rationnels pour le moment pour étayer cette position.


2

Joli!

Vient d'ajouter

        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();

Et c'est encore plus rapide (je suppose que string.Concat est appelé dans les deux exemples, mais le premier nécessite une sorte de traduction).

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
Cela prend exactement le même temps puisque les concaténations de chaînes basées sur les opérateurs sont traduites par le compilateur en appels à string.Concat(...). Elle est effectuée lors de la compilation et n'a donc aucun impact sur les performances d'exécution. Si vous exécutez vos tests plusieurs fois ou si vous les exécutez sur des échantillons de test plus grands, vous verrez qu'ils sont identiques.
Allon Guralnek le

2

Puisque je ne pense pas que les réponses ici couvrent tout, j'aimerais faire un petit ajout ici.

Console.WriteLine(string format, params object[] pars)appels string.Format. Le «+» implique la concaténation de chaînes. Je ne pense pas que cela ait toujours à voir avec le style; J'ai tendance à mélanger les deux styles en fonction du contexte dans lequel je suis.

Réponse courte

La décision à laquelle vous êtes confronté concerne l'allocation de chaînes. Je vais essayer de faire simple.

Dis que tu as

string s = a + "foo" + b;

Si vous exécutez ceci, il évaluera comme suit:

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

tmpici, ce n'est pas vraiment une variable locale, mais c'est une variable temporaire pour le JIT (elle est poussée sur la pile IL). Si vous poussez une chaîne sur la pile (telle queldstr dans IL pour les littéraux), vous placez une référence à un pointeur de chaîne sur la pile.

Le moment où vous appelez concat cette référence devient un problème, car il n'y a pas de référence de chaîne disponible contenant les deux chaînes. Cela signifie que .NET doit allouer un nouveau bloc de mémoire, puis le remplir avec les deux chaînes. La raison pour laquelle c'est un problème, c'est que l'attribution est relativement chère.

Ce qui change la question en: Comment pouvez-vous réduire le nombre d' concatopérations?

Donc, la réponse approximative est: string.Formatpour> 1 concat, '+' fonctionnera très bien pour 1 concat. Et si vous ne vous souciez pas de faire des optimisations de micro-performances, string.Formatcela fonctionnera très bien dans le cas général.

Une note sur la culture

Et puis il y a quelque chose qui s'appelle la culture ...

string.Formatvous permet d'utiliser CultureInfodans votre mise en forme. Un simple opérateur «+» utilise la culture actuelle.

C'est particulièrement une remarque importante si vous écrivez des formats de fichiers et f.ex. doubleles valeurs que vous «ajoutez» à une chaîne. Sur différentes machines, vous pourriez vous retrouver avec des chaînes différentes si vous ne l'utilisez pas string.Formatavec un fichier explicite CultureInfo.

Par ex. considérez ce qui se passe si vous changez un '.' pour un ',' lors de l'écriture de votre fichier de valeurs séparées par des virgules ... en néerlandais, le séparateur décimal est une virgule, donc votre utilisateur pourrait juste avoir une 'drôle' surprise.

Réponse plus détaillée

Si vous ne connaissez pas la taille exacte de la chaîne au préalable, il est préférable d'utiliser une stratégie comme celle-ci pour surallouer les tampons que vous utilisez. L'espace libre est d'abord rempli, après quoi les données sont copiées.

Croître signifie allouer un nouveau bloc de mémoire et copier les anciennes données dans le nouveau tampon. L'ancien bloc de mémoire peut alors être libéré. Vous obtenez le résultat final à ce stade: la culture est une opération coûteuse.

Le moyen le plus pratique de procéder consiste à utiliser une politique de surutilisation. La politique la plus courante consiste à surallouer les tampons en puissances de 2. Bien sûr, vous devez le faire un peu plus intelligemment que cela (car cela n'a aucun sens de passer de 1,2,4,8 si vous savez déjà que vous avez besoin de 128 caractères ) Mais tu vois ce que je veux dire. La politique garantit que vous n'avez pas besoin d'un trop grand nombre des opérations coûteuses que j'ai décrites ci-dessus.

StringBuilderest une classe qui surutilise essentiellement le tampon sous-jacent en puissances de deux. string.Formatutilise StringBuildersous le capot.

Cela fait de votre décision un compromis de base entre surallouer et ajouter (-multiple) (w / wo culture) ou simplement allouer et ajouter.


1

Personnellement, le second, car tout ce que vous utilisez est dans l'ordre direct dans lequel il sera affiché. Alors qu'avec le premier, vous devez faire correspondre les {0} et {1} avec la variable appropriée, ce qui est facile à gâcher.

Au moins, ce n'est pas aussi mauvais que le sprintf C ++ où si vous obtenez le mauvais type de variable, tout explosera.

De plus, comme le second est entièrement en ligne et qu'il n'a pas à faire de recherche et de remplacement pour toutes les {0} choses, ce dernier devrait être plus rapide ... même si je ne sais pas avec certitude.


1

J'aime en fait le premier car quand il y a beaucoup de variables entremêlées avec le texte, il me semble plus facile à lire. De plus, il est plus facile de traiter les guillemets lors de l'utilisation de la chaîne.Format (), euh, format. Voici une analyse décente de la concaténation de chaînes.


1

J'ai toujours suivi la route string.Format (). Pouvoir stocker des formats dans des variables comme l'exemple de Nathan est un grand avantage. Dans certains cas, je peux ajouter une variable mais une fois plus d'une variable est concaténée, je refactorise pour utiliser le formatage.


1

Oh, et juste pour être complet, ce qui suit est quelques ticks plus rapides que la concaténation normale:

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

1

Le premier (format) me semble meilleur. Il est plus lisible et vous ne créez pas d'objets chaîne temporaires supplémentaires.


1

J'étais curieux de savoir où en était StringBuilder avec ces tests. Résultats ci-dessous ...

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;
   }
}

Résultats:

Concat: 406 ticks
Concat: 356 ticks
Concat: 411 ticks
Concat: 299 ticks
Concat: 266 ticks
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

Selon le matériel de préparation MCSD, Microsoft suggère d'utiliser l'opérateur + pour traiter un très petit nombre de concaténations (probablement 2 à 4). Je ne sais toujours pas pourquoi, mais c'est quelque chose à considérer.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.