web-dev-qa-db-fra.com

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

J'ai un petit morceau de code qui analyse une valeur d'index pour déterminer une entrée de cellule dans Excel. Ça me fait penser ...

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?

111
Gavin Miller

Avant C # 6

Pour être honnête, je pense que la première version est plus simple - bien que je la simplifie pour:

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

Je soupçonne que d’autres réponses peuvent parler de la performance, mais pour être honnête, ce sera minimal si présent à all - et cette version de la concaténation n'a pas besoin d'analyser la chaîne de formatage.

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

ce qui est probablement la meilleure option, IMO.

108
Jon Skeet

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

  • La concaténation de chaînes est sans doute "plus sûre". Il m'est arrivé (et je l'ai vu 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érifie pas les paramètres par rapport à la chaîne de format et vous vous retrouvez avec une erreur d'exécution (c'est-à-dire, si vous avez la chance de ne pas l'avoir dans une méthode obscure, telle que la journalisation d'une erreur). Avec la concaténation, supprimer un paramètre est moins sujet aux erreurs. Vous pourriez prétendre que le risque d'erreur est très faible, mais cela peut se produire.

- La concaténation de chaînes autorise les valeurs nulles, pas String.Format. L'écriture "s1 + null + s2" Ne rompt pas, elle traite simplement la valeur null comme String.Empty. Cela peut dépendre de votre scénario spécifique. Dans certains cas, vous voudriez une erreur au lieu d’ignorer silencieusement un prénom null. Cependant, même dans cette situation, je préfère personnellement vérifier moi-même et générer des erreurs spécifiques au lieu de l'exception standard ArgumentNullException obtenue de String.Format.

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

Idea is le compilateur .NET est assez 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.Concat Est beaucoup plus rapide? Eh bien, nous pouvons tous jeter un coup d'œil à ce que String.Format Est en train de faire - vous serez surpris de la quantité de code nécessaire pour traiter la chaîne de formatage. En plus de cela (j'ai vu des commentaires concernant la consommation de mémoire), String.Format Utilise 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, tant pis, nous avons un peu d'espace perdu. Si l'argument est un objet personnalisé renvoyant un texte long sur ToString(), il se peut même qu'une réaffectation soit nécessaire (dans le pire des cas, bien entendu).

Par rapport à cela, la concaténation ne fait que gaspiller l’espace du tableau d’objets (pas trop, compte tenu du fait qu’il s’agit d’un tableau de références). Il n'y a pas d'analyse pour les spécificateurs de format ni d'intermédiaire StringBuilder. La surcharge de boxing/unboxing 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 de placer des chaînes de format dans des ressources vous permet de prendre en charge différentes langues sans manipuler le code (pensez à des scénarios dans lesquels les valeurs formatées changent d’ordre en fonction de la langue, c.-à-d. "Après {0} heures et {1} minutes" peut paraître très différent en japonais: ).


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

  • la meilleure façon (en termes de performances vs maintenabilité/lisibilité) pour moi est d'utiliser la concaténation de chaînes, sans aucun appel ToString()
  • si vous recherchez des performances, faites en sorte que la ToString() vous appelle pour éviter la boxe (je suis plutôt biaisé en faveur de la lisibilité) - comme pour 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.
153
Dan C.

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 réflecteur ) afin qu'il n'apporte aucun avantage en termes de performances, sauf si vous effectuez une concaténation importante. Cela sera plus lent pour votre scénario, mais la réalité est que cette décision d'optimisation des performances micro est inappropriée la plupart du temps et que vous devriez vraiment vous concentrer sur la lisibilité de votre code, sauf si vous êtes dans une boucle.

Dans les deux cas, écrivez d’abord pour la lisibilité, puis utilisez un profileur de performances pour identifier vos points chauds si vous pensez réellement avoir des problèmes de performances.

6

Pour un cas simple où il s’agit d’une simple concaténation, 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, string.Format pourrait être un peu plus lent, avec l'analyse de formatage et tout). Comme Jon Skeet, je préfère ne pas appeler explicitement .ToString(), car cela se fera implicitement par la surcharge string.Concat(string, object), et je pense que le code est plus propre et plus facile à lire sans lui. .

Mais pour plus que quelques concaténations (combien est subjective), je préfère définitivement string.Format. À un moment donné, je pense que la concaténation nuit à la lisibilité et aux performances.

S'il existe de nombreux paramètres dans la chaîne de formatage (là encore, "plusieurs" est subjectif), je préfère généralement inclure des index commentés sur les arguments de remplacement, afin de ne pas perdre la trace de la valeur qui 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]
);

Mise à 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.Format ici. Et oui, logiquement et lexicalement, c'est ce que j'ai fait. Mais les concaténations seront toutes optimisées par le compilateur1, puisqu'ils sont tous des littéraux de chaîne. Donc, au moment de l'exécution, il n'y aura qu'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 obsolète, sauf si vous êtes toujours bloqué avec C 5 ou plus. Nous avons maintenant chaînes interpolées , qui, pour la lisibilité, sont de loin supérieures à string.Format, Dans presque tous les cas. De nos jours, à moins que je ne fasse que concaténer une valeur directement au début ou à la fin d'un littéral de chaîne, j'utilise presque toujours une 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 appel à string.Format Par le compilateur et leurs résultats. sont concaténés au moment de l'exécution. Cela signifie que c'est un sacrifice de la performance d'exécution pour la lisibilité. La plupart du temps, c'est un sacrifice louable, car la pénalité d'exécution est négligeable. Dans le code de performances critiques, vous devrez peut-être définir différentes solutions.


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

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

...

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

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";
5
P Daddy

Si votre chaîne était plus complexe avec de nombreuses variables concaténées, je choisirais alors string.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 spartan .

3
Aaron Palmer

J'ai jeté un coup d'œil à String.Format (à l'aide de Reflector) qui crée un StringBuilder puis appelle AppendFormat dessus. C'est donc plus rapide que concat pour plusieurs brassages. Le plus rapide (je crois) serait de créer un constructeur de chaînes et de faire les appels à ajouter manuellement. Bien sûr, le nombre de "plusieurs" est à deviner. Je voudrais utiliser + (en fait & parce que je suis un VB programmeur principalement) pour quelque chose d'aussi simple que votre exemple. Comme il devient plus complexe, j'utilise String.Format. S'il y a BEAUCOUP de variables, alors J'irais pour un StringBuilder et Append, par exemple, nous avons un 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 - il n'y aura pas de boxing de rowIndex)
Ensuite, nous obtenons la dernière chaîne.
Si nous prenons l'exemple de

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

alors nous avons "C {0}" comme une chaîne
rowIndex est mis en boîte pour être passé à la fonction
Un nouveau constructeur de chaînes est créé
AppendFormat est appelé dans le générateur de chaînes. Je ne connais pas les détails du fonctionnement de AppendFormat, mais supposons qu’il soit ultra efficace, il va tout de même falloir convertir le rowIndex encadré en chaîne.
Convertissez ensuite le constructeur de chaînes en une nouvelle chaîne.
Je sais que StringBuilders tente d’empêcher les copies de mémoire inutiles, mais le format String.Format entraîne toujours une surcharge supplémentaire par rapport à la concaténation simple.

Si nous prenons maintenant un exemple avec quelques chaînes supplémentaires

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

nous avons 6 chaînes pour commencer, qui seront les mêmes pour 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 à l'aide de String, Format (ou StringBuilder).
Rappelez-vous que pour créer chaque chaîne intermédiaire, la chaîne précédente doit être copiée dans un nouvel emplacement mémoire, il ne s'agit pas simplement de l'allocation de mémoire qui est potentiellement lente.

3
pipTheGeek

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 Edge pour des scénarios plus compliqués. Mais c’est plutôt une impression instinctive de pouvoir mieux utiliser un tampon au lieu de produire plusieurs chaînes immuables, et non à partir de données réelles.

2
Joel Coehoorn

J'aime bien 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 et vous permet de formater vos paramètres.

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

1
CMS

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 en utilisant '+', il a fallu 1,6 seconde.

1
Kitemark76

Je suis d'accord avec beaucoup de points ci-dessus, un autre point que je pense devrait être mentionné est la maintenabilité du code. string.Format facilite le changement de code.

c'est-à-dire que j'ai un message "The user is not authorized for location " + location ou "The User is not authorized for location {0}"

si j'ai toujours voulu changer le message en disant: location + " does not allow this User Access" ou "{0} does not allow this User Access"

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

si utilisé dans plusieurs endroits peut gagner beaucoup de temps.

1
deankarn

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

0
Farzad J

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

0
Andrei Rînea

J'ai fait un peu de profilage de diverses méthodes de chaîne, y compris string.Format, StringBuilder et concaténation de chaînes. La concaténation de chaînes surpasse presque toujours les autres méthodes de construction de chaînes. Donc, si la performance est la clé, alors 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) Cependant, StringBuilder est probablement le plus efficace en ce qui concerne l'utilisation de la mémoire.

0
dviljoen