Database
 sql >> Base de données >  >> RDS >> Database

L'opérateur de chaîne "+" est-il si simple ?

Introduction

Un type de données chaîne est l'un des types de données fondamentaux, avec les types numériques (int, long, double) et logiques (booléens). Vous pouvez difficilement imaginer au moins un programme utile qui n'utilise pas ce type.

Sur la plate-forme .NET, le type de chaîne est présenté comme une classe String immuable. De plus, il est fortement intégré à l'environnement CLR et est également pris en charge par le compilateur C#.

Cet article est consacré à la concaténation - une opération effectuée sur les chaînes aussi souvent que l'opération d'addition sur les chiffres. Vous pensez peut-être :"Qu'y a-t-il à dire ?", après tout, nous connaissons tous l'opérateur de chaîne "+", mais il s'est avéré qu'il a ses propres bizarreries.

Spécification du langage pour l'opérateur de chaîne "+"

La spécification du langage C# fournit trois surcharges pour l'opérateur de chaîne "+" :

string operator + (string x, string y)

string operator + (string x, object y)

string operator + (object x, string y)

Si l'un des opérandes de la concaténation de chaîne est NULL, la chaîne vide est insérée. Sinon, tout argument, qui n'est pas une chaîne, est représenté comme une chaîne en appelant la méthode virtuelle ToString. Si la méthode ToString renvoie NULL, une chaîne vide est insérée. Il convient de noter que selon la spécification, cette opération ne doit jamais retourner NULL.

La description de l'opérateur est assez claire, cependant, si nous regardons l'implémentation de la classe String, nous trouvons une définition claire de seulement deux opérateurs "==" et "!=". Une question raisonnable se pose :que se passe-t-il dans les coulisses de la concaténation de chaînes ? Comment le compilateur gère-t-il l'opérateur de chaîne "+" ?

La réponse à cette question s'est avérée pas si difficile. Examinons de plus près la méthode statique String.Concat. La méthode String.Concat joint une ou plusieurs instances de la classe ou des vues String en tant que valeurs String d'une ou plusieurs instances de Object. Il existe les surcharges suivantes de cette méthode :

public static String Concat (String str0, String str1)

public static String Concat (String str0, String str1, String str2)

public static String Concat (String str0, String str1, String str2, String str3)

public static String Concat (params String[] values)

public static String Concat (IEnumerable <String> values)



public static String Concat (Object arg0)

public static String Concat (Object arg0, Object arg1)

public static String Concat (Object arg0, Object arg1, Object arg2)

public static String Concat (Object arg0, Object arg1, Object arg2, Object arg3, __arglist)



public static String Concat <T> (IEnumerable <T> values)

Détails

Supposons que nous ayons l'expression suivante s =a + b, où a et b sont des chaînes. Le compilateur le convertit en un appel d'une méthode statique Concat, c'est-à-dire

s = string.Concat (a, b)

L'opération de concaténation de chaînes, comme toute autre opération d'addition dans le langage C#, est associative à gauche.

Tout est clair avec deux lignes, mais que se passe-t-il s'il y a plus de lignes ? L'expression s =a + b + c, compte tenu de l'associativité à gauche de l'opération, pourrait être remplacée par :

s = string.Concat(string.Concat (a, b), c)

Cependant, étant donné la surcharge qui prend trois arguments, il sera converti en :

s = string.Concat (a, b, c)

La situation est similaire avec la concaténation de quatre chaînes. Pour concaténer 5 chaînes ou plus, nous avons la surcharge string.Concat (params string[]), il est donc nécessaire de prendre en compte la surcharge associée à l'allocation de mémoire pour un tableau.

Il convient également de noter que l'opérateur de concaténation de chaînes est entièrement associatif  :peu importe l'ordre dans lequel nous concaténons les chaînes, l'expression s =a + (b + c), malgré la priorité explicitement indiquée d'exécution de la concaténation, doit être traitée comme suit

s = (a + b) + c = string.Concat (a, b, c)

au lieu de l'attendu :

s = string.Concat (a, string.Concat (b, c))

Ainsi, pour résumer ce qui précède :l'opération de concaténation de chaînes est toujours représentée de gauche à droite et appelle la méthode statique String.Concat.

Optimisation du compilateur pour les chaînes littérales

Le compilateur C# a des optimisations relatives aux chaînes littérales. Par exemple, l'expression s ="a" + "b" + c, compte tenu de l'associativité à gauche de l'opérateur "+", est équivalente à s =​​("a" + "b") + c est converti en

s = string.Concat ("ab", c)

L'expression s =c + "a" + "b", malgré l'associativité à gauche de l'opération de concaténation (s =(c + "a") + "b") est convertie en

s = string.Concat (c, "ab")

En général, la position des littéraux n'a pas d'importance, le compilateur concatène tout ce qu'il peut et essaie alors seulement de sélectionner une surcharge appropriée de la méthode Concat. L'expression s =a + "b" + "c" + d est convertie en

s = string.Concat (a, "bc", d)

Les optimisations associées aux chaînes vides et NULL doivent également être mentionnées. Le compilateur sait que l'ajout d'une chaîne vide n'affecte pas le résultat de la concaténation, donc l'expression s =a + "" + b est convertie en

s = string.Concat (a, b),

au lieu de l'attendu

s = string.Concat (a, "", b)

De même, avec la chaîne const dont la valeur est NULL, on a :

const string nullStr = null;

s = a + nullStr + b;

est converti en

s = string.Concat (a, b)

L'expression s =a + nullStr est convertie en s =a ?? "", si a est une chaîne, et l'appel de la méthode string.Concat(a), si a n'est pas une chaîne, par exemple, s =17 + nullStr, il est converti en s =string.Concat (17) .

Une fonctionnalité intéressante associée à l'optimisation du traitement littéral et à l'associativité à gauche de l'opérateur de chaîne "+".

Considérons l'expression :

var s1 = 17 + 17 + "abc";

compte tenu de l'associativité à gauche, cela équivaut à

var s1 = (17 + 17) + "abc"; // сalling the string.Concat method (34, "abc")

Par conséquent, au moment de la compilation, les chiffres sont ajoutés, de sorte que le résultat sera 34abc.

En revanche, l'expression

var s2 = "abc" + 17 + 17;

est équivalent à

var s2 = ( "abc" + 17) + 17; // calling the string.Concat method ("abc", 17, 17)

le résultat sera abc1717.

Donc, voilà, le même opérateur de concaténation conduit à des résultats différents.

String.Concat VS StringBuilder.Append

Il est nécessaire de dire quelques mots sur cette comparaison. Considérons le code suivant :

string name = "Timur";

string surname = "Guev";

string patronymic = "Ahsarbecovich";

string fio = surname + name + patronymic;

Il peut être remplacé par le code utilisant StringBuilder :

var sb = new StringBuilder ();

sb.Append (surname);

sb.Append (name);

sb.Append (patronymic);

string fio = sb.ToString ();

Cependant, dans ce cas, nous n'obtiendrons guère d'avantages en utilisant StringBuilder. Outre le fait que le code est devenu moins lisible, il est devenu plus ou moins efficace, puisque l'implémentation de la méthode Concat calcule la longueur de la chaîne résultante et n'alloue la mémoire qu'une seule fois, contrairement à StringBuilder qui ne sait rien de la longueur de la chaîne résultante.

Implémentation de la méthode Concat pour 3 chaînes :

public static string Concat (string str0, string str1, string str2)

{

if (str0 == null && str1 == null && str2 == null)

return string.Empty;

if (str0 == null)

str0 = string.Empty;

if (str1 == null)

str1 = string.Empty;

if (str2 == null)

str2 = string.Empty;

string dest = string.FastAllocateString (str0.Length + str1.Length + str2.Length); // Allocate memory for strings

string.FillStringChecked (dest, 0, str0); /

string.FillStringChecked (dest, str0.Length, str1);

string.FillStringChecked (dest, str0.Length + str1.Length, str2);

return dest;

}

Opérateur "+" en Java

Quelques mots sur l'opérateur de chaîne "+" en Java. Bien que je ne programme pas en Java, je suis néanmoins intéressé par la façon dont cela fonctionne là-bas. Le compilateur Java optimise l'opérateur "+" pour qu'il utilise la classe StringBuilder et appelle la méthode append.

Le code précédent est converti en

String fio = new StringBuilder(String.valueOf(surname)).append(name).append (patronymic).ToString()

Il convient de noter qu'ils ont intentionnellement refusé une telle optimisation en C #, Eric Lippert a un article sur ce sujet. Le fait est qu'une telle optimisation n'est pas l'optimisation en tant que telle, c'est la réécriture de code. En outre, les créateurs du langage C# estiment que les développeurs doivent se familiariser avec les aspects de l'utilisation de la classe String et, si nécessaire, passer à StringBuilder.

Au fait, c'est Eric Lippert qui a travaillé sur l'optimisation du compilateur C# associé à la concaténation de chaînes.

Conclusion

Peut-être, à première vue, il peut sembler étrange que la classe String ne définisse pas l'opérateur "+" jusqu'à ce que nous pensions à la capacité d'optimisation du compilateur liée à la visibilité d'un fragment de code plus grand. Par exemple, si l'opérateur "+" était défini dans la classe String, l'expression s =a + b + c + d conduirait à la création de deux chaînes intermédiaires, un seul appel de la chaîne.Concat (a, b, c, d) permet d'effectuer la concaténation plus efficacement.