Combien d'objets String seront créés lors de l'utilisation d'un signe plus?

115

Combien d'objets String seront créés lors de l'utilisation d'un signe plus dans le code ci-dessous?

String result = "1" + "2" + "3" + "4";

Si c'était comme ci-dessous, j'aurais dit trois objets String: "1", "2", "12".

String result = "1" + "2";

Je sais également que les objets String sont mis en cache dans le pool / table String Intern pour améliorer les performances, mais ce n'est pas la question.

La lumière
la source
Les chaînes ne sont internées que si vous appelez explicitement String.Intern.
Joe White
7
@JoeWhite: le sont-ils?
Igor Korkhov
13
Pas assez. Tous les littéraux de chaîne sont automatiquement internés. Les résultats des opérations sur les chaînes ne le sont pas.
Stefan Paul Noack
De plus, dans l'exemple OP, il n'y a qu'une seule constante de chaîne et elle est internée. Je vais mettre à jour ma réponse pour illustrer.
Chris Shain
+1. Pour un exemple réel de la nécessité de coder une chaîne de caractères dans ce style, la section Exemples de msdn.microsoft.com/en-us/library/ ... en a une qui ne serait pas possible si le compilateur était incapable de l'optimiser à une seule constante, en raison des contraintes sur les valeurs affectées aux paramètres d'attribut.
ClickRick

Réponses:

161

Étonnamment, cela dépend.

Si vous faites cela dans une méthode:

void Foo() {
    String one = "1";
    String two = "2";
    String result = one + two + "34";
    Console.Out.WriteLine(result);
}

puis le compilateur semble émettre le code en utilisant String.Concatcomme @Joachim a répondu (+1 pour lui btw).

Si vous les définissez comme des constantes , par exemple:

const String one = "1";
const String two = "2";
const String result = one + two + "34";

ou comme littéraux , comme dans la question d'origine:

String result = "1" + "2" + "3" + "4";

alors le compilateur optimisera ces +signes. C'est équivalent à:

const String result = "1234";

De plus, le compilateur supprimera les expressions constantes superflues et ne les émettra que si elles sont utilisées ou exposées. Par exemple, ce programme:

const String one = "1";
const String two = "1";
const String result = one + two + "34";

public static void main(string[] args) {
    Console.Out.WriteLine(result);
}

Ne génère qu'une seule chaîne - la constante result(égale à "1234"). oneet twon'apparaissent pas dans l'IL résultante.

Gardez à l'esprit qu'il peut y avoir d'autres optimisations lors de l'exécution. Je vais juste par ce que IL est produit.

Enfin, en ce qui concerne l'internement, les constantes et les littéraux sont internés, mais la valeur qui est internée est la valeur constante résultante dans l'IL, pas le littéral. Cela signifie que vous pourriez obtenir encore moins d'objets chaîne que vous ne le pensez, car plusieurs constantes ou littéraux définis de manière identique seront en fait le même objet! Ceci est illustré par ce qui suit:

public class Program
{
    private const String one = "1";
    private const String two = "2";
    private const String RESULT = one + two + "34";

    static String MakeIt()
    {
        return "1" + "2" + "3" + "4";
    }   

    static void Main(string[] args)
    {
        string result = "1" + "2" + "34";

        // Prints "True"
        Console.Out.WriteLine(Object.ReferenceEquals(result, MakeIt()));

        // Prints "True" also
        Console.Out.WriteLine(Object.ReferenceEquals(result, RESULT));
        Console.ReadKey();
    }
}

Dans le cas où les chaînes sont concaténées dans une boucle (ou autrement dynamiquement), vous vous retrouvez avec une chaîne supplémentaire par concaténation. Par exemple, ce qui suit crée 12 instances de chaîne: 2 constantes + 10 itérations, chacune aboutissant à une nouvelle instance de chaîne:

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a";
        Console.ReadKey();
    }
}

Mais (aussi étonnamment), plusieurs concaténations consécutives sont combinées par le compilateur en une seule concaténation multi-chaînes. Par exemple, ce programme ne produit également que 12 instances de chaîne! En effet, « même si vous utilisez plusieurs opérateurs + dans une instruction, le contenu de la chaîne n'est copié qu'une seule fois » .

public class Program
{
    static void Main(string[] args)
    {
        string result = "";
        for (int i = 0; i < 10; i++)
            result += "a" + result;
        Console.ReadKey();
    }
}
Chris Shain
la source
qu'en est-il de String result = "1" + "2" + trois + quatre; où deux et trois sont déclarés comme la chaîne trois = "3"; Chaîne quatre = "4" ;?
The Light
Même cela se traduit par une chaîne. Je l'ai juste parcouru LinqPad pour me vérifier.
Chris Shain
1
@Servy - Le commentaire semble avoir été mis à jour. Lorsque vous modifiez un commentaire, il n'est pas marqué comme étant modifié.
Security Hound
1
Un cas qui serait bien à considérer pour l'exhaustivité est la concaténation dans une boucle. Par exemple, combien d'objets chaîne le code suivant alloue-t-il:string s = ""; for (int i = 0; i < n; i++) s += "a";
Joren
1
J'utilise LINQPad ( linqpad.net ) ou Reflector ( reflector.net ). Le premier vous montre l'IL d'extraits de code arbitraires, le second décompile les assemblys en IL et peut régénérer un C # équivalent à partir de cet IL. Il existe également un outil intégré appelé ILDASM ( msdn.microsoft.com/en-us/library/f7dy01k1(v=vs.80).aspx ) Comprendre IL est une chose délicate - voir codebetter.com/raymondlewallen/2005/ 02/07 /…
Chris Shain
85

La réponse de Chris Shain est très bonne. En tant que personne qui a écrit l'optimiseur de concaténation de chaînes, je voudrais simplement ajouter deux points intéressants supplémentaires.

Le premier est que l'optimiseur de concaténation ignore essentiellement les parenthèses et l'associativité gauche lorsqu'il peut le faire en toute sécurité. Supposons que vous ayez une méthode M () qui renvoie une chaîne. Si tu le dis:

string s = M() + "A" + "B";

alors le compilateur explique que l'opérateur d'addition reste associatif, et c'est donc la même chose que:

string s = ((M() + "A") + "B");

Mais ça:

string s = "C" + "D" + M();

est le même que

string s = (("C" + "D") + M());

c'est donc la concaténation de la chaîne constante "CD" avec M().

En fait, l'optimiseur de concaténation se rend compte que la concaténation de chaînes est associative et génère String.Concat(M(), "AB")pour le premier exemple, même si cela viole l'associativité gauche.

Vous pouvez même faire ceci:

string s = (M() + "E") + ("F" + M()));

et nous allons toujours générer String.Concat(M(), "EF", M()).

Le deuxième point intéressant est que les chaînes nulles et vides sont optimisées. Donc si vous faites ceci:

string s = (M() + "") + (null + M());

tu auras String.Concat(M(), M())

Une question intéressante se pose alors: qu'en est-il de cela?

string s = M() + null;

Nous ne pouvons pas optimiser cela jusqu'à

string s = M();

car M()pourrait renvoyer null, mais String.Concat(M(), null)retournerait une chaîne vide si M()renvoie null. Donc ce que nous faisons, c'est plutôt réduire

string s = M() + null;

à

string s = M() ?? "";

Démontrant ainsi que la concaténation de chaînes n'a pas besoin d'appeler String.Concatdu tout.

Pour plus d'informations sur ce sujet, voir

Pourquoi String.Concat n'est-il pas optimisé pour StringBuilder.Append?

Eric Lippert
la source
Je pense que quelques erreurs se sont peut-être glissées là-dedans. Sûrement, ("C" + "D") + M())génère String.Concat("CD", M()), non String.Concat(M(), "AB"). Et plus bas, (M() + "E") + (null + M())devrait générer String.Concat(M(), "E", M()), non String.Concat(M(), M()).
hammar
21
+1 pour le paragraphe de départ. :) Des réponses comme celle-ci sont ce qui m'étonne toujours à propos de Stack Overflow.
brichins
23

J'ai trouvé la réponse chez MSDN. Une.

Guide pratique pour concaténer plusieurs chaînes (Guide de programmation C #)

La concaténation est le processus d'ajout d'une chaîne à la fin d'une autre chaîne. Lorsque vous concaténez des littéraux de chaîne ou des constantes de chaîne à l'aide de l'opérateur +, le compilateur crée une seule chaîne. Aucune concaténation d'exécution ne se produit. Cependant, les variables chaîne ne peuvent être concaténées qu'au moment de l'exécution. Dans ce cas, vous devez comprendre les implications des différentes approches sur les performances.

David
la source
22

Juste un. Le compilateur C # pliera les constantes de chaîne et par conséquent, il se compile essentiellement en

String result = "1234";
JaredPar
la source
J'ai pensé que chaque fois que vous utilisez "", cela crée un objet String.
The Light
1
@William en général oui. Mais un pliage constant supprimera les étapes intermédiaires inutiles
JaredPar
13

Je doute que cela soit imposé par une norme ou une spécification. Une version peut probablement faire quelque chose de différent d'une autre.

Variable misérable
la source
3
Il s'agit d'un comportement documenté au moins pour le compilateur C # de Microsoft pour VS 2008 et 2010 (voir la réponse de @ David-Stratton). Cela dit, vous avez raison - pour autant que je sache d'après une lecture rapide, la spécification C # ne le spécifie pas et cela devrait probablement être considéré comme un détail d'implémentation.
Chris Shain
13

Premièrement, comme ils sont statiques, le compilateur pourra l'optimiser en une seule chaîne au moment de la compilation.

S'ils avaient été dynamiques, ils auraient été optimisés pour un seul appel à String.Concat (chaîne, chaîne, chaîne, chaîne) .

Joachim Isaksson
la source