Quand est-il préférable d'utiliser String.Format par rapport à la concaténation de chaînes?

120

J'ai un petit morceau de code qui analyse une valeur d'index pour déterminer une entrée de cellule dans Excel. Cela me fait réfléchir ...

Quelle est la différence entre

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

et

xlsSheet.Write(string.Format("C{0}", rowIndex), null, title);

Est-ce que l'un est meilleur que l'autre? Et pourquoi?

Gavin Miller
la source
4
Ceci est similaire à stackoverflow.com/questions/16432/…
Jonathan S.
duplication possible de Pourquoi utiliser String.Format?
Druid

Réponses:

115

Avant C # 6

Pour être honnête, je pense que la première version est plus simple - même si je la simplifierais comme suit:

xlsSheet.Write("C" + rowIndex, null, title);

Je soupçonne que d'autres réponses peuvent parler de l'impact sur les performances, mais pour être honnête, ce sera minime, voire présent - et cette version de concaténation n'a pas besoin d'analyser la chaîne de format.

Les chaînes de format sont idéales pour la localisation, etc., mais dans un cas comme celui-ci, la concaténation est plus simple et fonctionne tout aussi bien.

Avec C # 6

L'interpolation de chaîne simplifie la lecture de beaucoup de choses en C # 6. Dans ce cas, votre deuxième code devient:

xlsSheet.Write($"C{rowIndex}", null, title);

qui est probablement la meilleure option, l'OMI.

Jon Skeet
la source
Je sais je sais. Il a été fait en plaisantant (j'ai lu le lien btw auparavant, ce qui était une bonne lecture)
nawfal
Jon. J'ai toujours été fan de M. Richter et j'ai suivi religieusement des conseils sur la boxe, etc. Cependant, après avoir lu votre (ancien) article, je suis maintenant converti. Merci
stevethethread
3
@ mbomb007: C'est maintenant sur codeblog.jonskeet.uk/2008/10/08
Jon Skeet
4
Maintenant que C # 6 est disponible, vous pouvez utiliser la nouvelle syntaxe d'interpolation de chaîne pour ce que je pense être une lisibilité encore plus facile:xlsSheet.Write($"C{rowIndex}", null, title);
HotN
158

Ma préférence initiale (venant d'un arrière-plan C ++) était pour String.Format. J'ai laissé tomber ceci plus tard pour les raisons suivantes:

  • La concaténation de chaînes est sans doute "plus sûre". Il m'est arrivé (et j'ai vu cela arriver à plusieurs autres développeurs) de supprimer un paramètre, ou de gâcher l'ordre des paramètres par erreur. Le compilateur ne vérifiera pas les paramètres par rapport à la chaîne de format et vous vous retrouverez avec une erreur d'exécution (c'est-à-dire si vous avez la chance de ne pas l'avoir dans une méthode obscure, comme la journalisation d'une erreur). Avec la concaténation, la suppression d'un paramètre est moins sujette aux erreurs. Vous pourriez soutenir que le risque d'erreur est très faible, mais cela peut arriver.

- La concaténation de chaînes autorise les valeurs nulles, String.Formatnon. L'écriture " s1 + null + s2" ne casse pas, elle traite simplement la valeur nulle comme String.Empty. Eh bien, cela peut dépendre de votre scénario spécifique - il y a des cas où vous souhaitez une erreur au lieu d'ignorer silencieusement un FirstName nul. Cependant, même dans cette situation, je préfère personnellement vérifier moi-même les nulls et lancer des erreurs spécifiques au lieu de l'exception ArgumentNullException standard que j'obtiens de String.Format.

  • La concaténation de chaînes fonctionne mieux. Certains des articles ci-dessus le mentionnent déjà (sans vraiment expliquer pourquoi, ce qui m'a déterminé à écrire cet article :).

L'idée est que le compilateur .NET est suffisamment intelligent pour convertir ce morceau de code:

public static string Test(string s1, int i2, int i3, int i4, 
        string s5, string s6, float f7, float f8)
{
    return s1 + " " + i2 + i3 + i4 + " ddd " + s5 + s6 + f7 + f8;
}

pour ça:

public static string Test(string s1, int i2, int i3, int i4,
            string s5, string s6, float f7, float f8)
{
    return string.Concat(new object[] { s1, " ", i2, i3, i4, 
                    " ddd ", s5, s6, f7, f8 });
}

Ce qui se passe sous le capot de String.Concat est facile à deviner (utilisez Reflector). Les objets du tableau sont convertis en leur chaîne via ToString (). Ensuite, la longueur totale est calculée et une seule chaîne est allouée (avec la longueur totale). Enfin, chaque chaîne est copiée dans la chaîne résultante via wstrcpy dans un morceau de code non sécurisé.

Raisons String.Concatest beaucoup plus rapide? Eh bien, nous pouvons tous voir ce qui String.Formatse passe - vous serez surpris de la quantité de code nécessaire pour traiter la chaîne de format. En plus de cela (j'ai vu des commentaires concernant la consommation de mémoire), String.Formatutilise un StringBuilder en interne. Voici comment:

StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));

Donc, pour chaque argument passé, il réserve 8 caractères. Si l'argument est une valeur à un chiffre, alors tant pis, nous avons un espace perdu. Si l'argument est un objet personnalisé renvoyant du texte long ToString(), une réallocation peut même être nécessaire (dans le pire des cas, bien sûr).

Par rapport à cela, la concaténation ne gaspille que l'espace du tableau d'objets (pas trop, en tenant compte que c'est un tableau de références). Il n'y a pas d'analyse pour les spécificateurs de format et aucun StringBuilder intermédiaire. La surcharge de boxe / déballage est présente dans les deux méthodes.

La seule raison pour laquelle j'opterais pour String.Format est lorsque la localisation est impliquée. Le fait d'insérer des chaînes de format dans les ressources vous permet de prendre en charge différentes langues sans modifier le code (pensez aux scénarios où les valeurs formatées changent d'ordre en fonction de la langue, c'est-à-dire que "après {0} heures et {1} minutes" peut être assez différent en japonais: ).


Pour résumer mon premier (et assez long) post:

  • le meilleur moyen (en termes de performances vs maintenabilité / lisibilité) pour moi est d'utiliser la concaténation de chaînes, sans aucun ToString()appel
  • si vous êtes après la performance, faites les ToString()appels vous-même pour éviter la boxe (je suis quelque peu biaisé pour la lisibilité) - comme la première option de votre question
  • si vous montrez des chaînes localisées à l'utilisateur (pas le cas ici), String.Format()a un bord.
Dan C.
la source
5
1) string.Formatest «sûr» lors de l'utilisation de ReSharper; c'est-à-dire qu'il est aussi sûr que tout autre code qui peut être utilisé [incorrectement]. 2) string.Format ne permet un « sûr » null: string.Format("A{0}B", (string)null)résultats dans « AB ». 3) Je me soucie rarement de ce niveau de performance (et à cette fin, c'est un jour rare où je me retire StringBuilder) ...
D'accord sur 2), je modifierai l'article. Je ne peux pas vérifier si cela était sûr dans la version 1.1, mais le dernier framework est en effet sans danger.
Dan C.
La chaîne string.Concat est-elle toujours utilisée si l'un des opérandes est un appel de méthode avec une valeur de retour, plutôt que d'être un paramètre ou une variable?
Richard Collette
2
@RichardCollette Oui, String.Concat est utilisé même si vous concaténez les valeurs de retour des appels de méthode, par exemple, il string s = "This " + MyMethod(arg) + " is a test";est compilé en un String.Concat()appel en mode Release.
Dan C.
Réponse fantastique; très bien écrit et expliqué.
Frank V
6

Je pense que la première option est plus lisible et que cela devrait être votre principale préoccupation.

xlsSheet.Write("C" + rowIndex.ToString(), null, title);

string.Format utilise un StringBuilder sous le capot (vérifiez avec le réflecteur ) donc il n'aura aucun avantage en termes de performances à moins que vous ne fassiez une quantité significative de concaténation. Ce sera plus lent pour votre scénario, mais la réalité est que cette décision d'optimisation des micro performances est inappropriée la plupart du temps et vous devriez vraiment vous concentrer sur la lisibilité de votre code à moins que vous ne soyez dans une boucle.

Dans tous les cas, écrivez d'abord pour la lisibilité, puis utilisez un profileur de performances pour identifier vos hotspots si vous pensez vraiment avoir des problèmes de performances.

Martin Hollingsworth
la source
5

Pour un cas simple où il s'agit d'une simple concaténation unique, je pense que cela ne vaut pas la complexité de string.Format(et je n'ai pas testé, mais je soupçonne que pour un cas simple comme celui-ci, cela string.Format pourrait être légèrement plus lent, avec l'analyse de la chaîne de format et tout). Comme Jon Skeet, je préfère ne pas appeler explicitement .ToString(), car cela sera fait implicitement par la string.Concat(string, object)surcharge, et je pense que le code est plus propre et plus facile à lire sans lui.

Mais pour plus de quelques concaténations (combien est subjectif), je préfère définitivement string.Format. À un certain moment, je pense que la lisibilité et les performances souffrent inutilement de la concaténation.

S'il y a beaucoup de paramètres dans la chaîne de format (encore une fois, "beaucoup" est subjectif), je préfère généralement inclure des indices commentés sur les arguments de remplacement, de peur de perdre la trace de quelle valeur va à quel paramètre. Un exemple artificiel:

Console.WriteLine(
    "Dear {0} {1},\n\n" +

    "Our records indicate that your {2}, \"{3}\", is due for {4} {5} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary",

    /*0*/client.Title,
    /*1*/client.LastName,
    /*2*/client.Pet.Animal,
    /*3*/client.Pet.Name,
    /*4*/client.Pet.Gender == Gender.Male ? "his" : "her",
    /*5*/client.Pet.Schedule[0]
);

Mettre à jour

Il me semble que l'exemple que j'ai donné est un peu déroutant, car il semble que j'ai utilisé à la fois la concaténation et string.Formatici. Et oui, logiquement et lexiquement, c'est ce que j'ai fait. Mais les concaténations seront toutes optimisées par le compilateur 1 , car ce sont toutes des chaînes littérales. Donc, au moment de l'exécution, il y aura une seule chaîne. Je suppose donc que je devrais dire que je préfère éviter de nombreuses concaténations au moment de l'exécution .

Bien sûr, la plupart de ce sujet est désormais obsolète, à moins que vous ne soyez toujours bloqué en utilisant C # 5 ou une version antérieure. Maintenant, nous avons des chaînes interpolées , qui, pour la lisibilité, sont bien supérieures à string.Format, dans presque tous les cas. Ces jours-ci, à moins que je ne concatène une valeur directement au début ou à la fin d'une chaîne littérale, j'utilise presque toujours l'interpolation de chaîne. Aujourd'hui, j'écrirais mon exemple précédent comme ceci:

Console.WriteLine(
    $"Dear {client.Title} {client.LastName},\n\n" +

    $"Our records indicate that your {client.Pet.Animal}, \"{client.Pet.Name}\", " +
    $"is due for {(client.Pet.Gender == Gender.Male ? "his" : "her")} " +
    $"{client.Pet.Schedule[0]} shots.\n" +
    "Please call our office at 1-900-382-5633 to make an appointment.\n\n" +

    "Thank you,\n" +
    "Eastern Veterinary"
);

Vous perdez ainsi la concaténation au moment de la compilation. Chaque chaîne interpolée est transformée en un appel string.Formatpar le compilateur et leurs résultats sont concaténés au moment de l'exécution. Cela signifie qu'il s'agit d'un sacrifice des performances d'exécution pour la lisibilité. La plupart du temps, c'est un sacrifice qui en vaut la peine, car la pénalité d'exécution est négligeable. Dans le code critique de performances, cependant, vous devrez peut-être profiler différentes solutions.


1 Vous pouvez voir ceci dans la spécification C # :

... les constructions suivantes sont autorisées dans les expressions constantes:

...

  • L'opérateur binaire + ... prédéfini ...

Vous pouvez également le vérifier avec un petit code:

const string s =
    "This compiles successfully, " +
    "and you can see that it will " +
    "all be one string (named `s`) " +
    "at run time";
Papa
la source
1
Pour info, vous pouvez utiliser @ "... chaîne multi-lignes" au lieu de toutes les concaténations.
Aaron Palmer
Oui, mais vous devez ensuite justifier votre chaîne à gauche. Les chaînes @ incluent toutes les nouvelles lignes et les caractères de tabulation entre les guillemets.
P Daddy
Je sais que c'est vieux, mais c'est un cas où je dirais mettre la chaîne de format dans un fichier resx.
Andy
2
Wow, tout le monde se concentre sur la chaîne littérale, plutôt que sur le cœur du problème.
P Daddy
heheh - Je remarquais juste la concaténation de cordes à l'intérieur de votreString.Format()
Kristopher
3

Si votre chaîne était plus complexe avec de nombreuses variables concaténées, je choisirais la chaîne.Format (). Mais pour la taille de la chaîne et le nombre de variables concaténées dans votre cas, j'irais avec votre première version, c'est plus spartiate .

Aaron Palmer
la source
3

J'ai jeté un coup d'œil à String.Format (en utilisant Reflector) et il crée en fait un StringBuilder puis appelle AppendFormat dessus. Il est donc plus rapide que concat pour plusieurs brassages. Le plus rapide (je crois) serait de créer un StringBuilder et de faire les appels à Append manuellement. Bien sûr, le nombre de "plusieurs" est à deviner. J'utiliserais + (en fait et parce que je suis principalement un programmeur VB) pour quelque chose d'aussi simple que votre exemple. Au fur et à mesure que cela devient plus complexe, j'utilise String.Format. S'il y a BEAUCOUP de variables, j'opterais pour un StringBuilder et un Append, par exemple, nous avons du code qui construit du code, j'utilise une ligne de code réel pour générer une ligne de code généré.

Il semble y avoir des spéculations sur le nombre de chaînes créées pour chacune de ces opérations, prenons donc quelques exemples simples.

"C" + rowIndex.ToString();

"C" est déjà une chaîne.
rowIndex.ToString () crée une autre chaîne. (@manohard - aucun encadrement de rowIndex ne se produira)
Ensuite, nous obtenons la chaîne finale.
Si nous prenons l'exemple de

String.Format("C(0)",rowIndex);

puis nous avons "C {0}" comme chaîne
rowIndex est encadrée pour être transmise à la fonction
Un nouveau stringbuilder est créé
AppendFormat est appelé sur le string builder - je ne connais pas les détails de la façon dont AppendFormat fonctionne mais supposons que c'est ultra efficace, il va encore falloir convertir le rowIndex encadré en une chaîne.
Ensuite, convertissez le constructeur de chaînes en une nouvelle chaîne.
Je sais que StringBuilders tente d'empêcher des copies de mémoire inutiles, mais le String.Format se termine toujours par une surcharge supplémentaire par rapport à la concaténation simple.

Si nous prenons maintenant un exemple avec quelques chaînes de plus

"a" + rowIndex.ToString() + "b" + colIndex.ToString() + "c" + zIndex.ToString();

nous avons 6 chaînes pour commencer, qui seront les mêmes dans tous les cas.
En utilisant la concaténation, nous avons également 4 chaînes intermédiaires plus le résultat final. Ce sont ces résultats intermédiaires qui sont éliminés en utilisant String, Format (ou un StringBuilder).
N'oubliez pas que pour créer chaque chaîne intermédiaire, la précédente doit être copiée dans un nouvel emplacement mémoire, ce n'est pas seulement l'allocation mémoire qui est potentiellement lente.

pipTheGeek
la source
4
Nitpick. Dans le "a" + ... + "b" + ... + "c" + ..., vous n'aurez pas réellement 4 chaînes intermédiaires. Le compilateur générera un appel à la méthode statique String.Concat (params string [] values), et ils seront tous concaténés en même temps. Je préférerais toujours string.Format pour des raisons de lisibilité, cependant.
P Daddy
2

J'aime String.Format car il peut rendre votre texte formaté beaucoup plus facile à suivre et à lire que la concaténation en ligne, il est également beaucoup plus flexible vous permettant de formater vos paramètres, mais pour de courtes utilisations comme la vôtre, je ne vois aucun problème à concaténer.

Pour les concaténations à l'intérieur de boucles ou dans de grandes chaînes, vous devez toujours essayer d'utiliser la classe StringBuilder.

CMS
la source
2

Cet exemple est probablement trop trivial pour remarquer une différence. En fait, je pense que dans la plupart des cas, le compilateur peut optimiser toute différence.

Cependant, si je devais deviner, je donnerais string.Format()un avantage pour des scénarios plus compliqués. Mais c'est plus un sentiment instinctif qu'il est susceptible de faire un meilleur travail en utilisant un tampon au lieu de produire plusieurs chaînes immuables, et non sur la base de données réelles.

Joel Coehoorn
la source
1

Je suis d'accord avec beaucoup de points ci-dessus, un autre point qui, je crois, devrait être mentionné est la maintenabilité du code. string.Format permet un changement de code plus facile.

ie j'ai un message "The user is not authorized for location " + locationou "The User is not authorized for location {0}"

si jamais je voulais changer le message pour dire: location + " does not allow this User Access"ou "{0} does not allow this User Access"

avec string.Format tout ce que j'ai à faire est de changer la chaîne. pour la concaténation, je dois modifier ce message

s'il est utilisé à plusieurs endroits, il peut gagner du temps.

Deankarn
la source
1

J'avais l'impression que string.format était plus rapide, il semble être 3 fois plus lent dans ce test

string concat = "";
        System.Diagnostics.Stopwatch sw1 = new System.Diagnostics.Stopwatch    ();
        sw1.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = string.Format("{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}","1", "2" , "3" , "4" , "5" , "6" , "7" , "8" , "9" , "10" , i);
        }
        sw1.Stop();
        Response.Write("format: "  + sw1.ElapsedMilliseconds.ToString());
        System.Diagnostics.Stopwatch sw2 = new System.Diagnostics.Stopwatch();
        sw2.Start();
        for (int i = 0; i < 10000000; i++)
        {
            concat = "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10" + i;
        }
        sw2.Stop();

string.format a pris 4,6 secondes et lors de l'utilisation de '+', il a fallu 1,6 secondes.

Kitemark76
la source
7
Le compilateur reconnaît "1" + "2" + "3" + "4" + "5" + "6" + "7" + "8" + "9" + "10"comme une chaîne littérale, donc la ligne devient effectivement "12345678910" + ice qui est plus rapide que la précédentestring.Format(...)
wertzui
0

string.Format est probablement un meilleur choix lorsque le modèle de format ("C {0}") est stocké dans un fichier de configuration (tel que Web.config / App.config)

Andrei Rînea
la source
0

J'ai fait un peu de profilage de diverses méthodes de chaîne, y compris string.Format, StringBuilder et la concaténation de chaînes. La concaténation de chaînes surpassait presque toujours les autres méthodes de création de chaînes. Donc, si la performance est la clé, c'est mieux. Cependant, si les performances ne sont pas critiques, je trouve personnellement que string.Format est plus facile à suivre dans le code. (Mais c'est une raison subjective) StringBuilder est probablement le plus efficace en ce qui concerne l'utilisation de la mémoire.

dviljoen
la source
0

Je préfère String.Format en ce qui concerne les performances

Farzad J
la source
-1

La concaténation de chaînes prend plus de mémoire que String.Format. La meilleure façon de concaténer des chaînes consiste donc à utiliser String.Format ou System.Text.StringBuilder Object.

Prenons le premier cas: "C" + rowIndex.ToString () Supposons que rowIndex est un type de valeur donc la méthode ToString () doit Box pour convertir la valeur en String, puis CLR crée de la mémoire pour la nouvelle chaîne avec les deux valeurs incluses.

Où en tant que string.Format attend le paramètre d'objet et prend dans rowIndex comme un objet et le convertit en chaîne en interne, il y aura de la boxe, mais c'est intrinsèque et cela ne prendra pas autant de mémoire que dans le premier cas.

Pour les cordes courtes, cela n'a pas d'importance, je suppose ...


la source