La programmation, tous langages confondus, implique souvent manipuler des chaînes de caractères. Pour cette raison, il est important de comprendre comment utiliser correctement les types et les mécanismes en ce sens de votre langage de prédilection, et il se trouve que les chaînes de caractères peuvent être radicalement différentes d'un langage à l'autre.
Cet article porte plus spécifiquement sur l'utilisation du type string de C#.
Le type string de C# est un alias pour le type System.String du cadriciel .NET. Pour cette raison, il offre une gamme de services que vous trouverez aussi dans les autres langages de la même plateforme.
Quelques caractéristiques notables du type string de C# suivent.
Le type string de C# est un type « référence », ce qui signifie que dans l'extrait suivant :
string s0 = "J'aime mon prof";
string s1 = s0;
... il n'y a qu'une seule string en pratique, et que les références s0 et s1 mènent toutes deux au même objet. Cela signifie qu'une string peut être null (notez qu'il faudra écrire string? au lieu de string pour qu'une string soit null à partir de C# v. 8.0).
Les instances de string en C# sont immuables. Cela signifie qu'il n'y a pas de services permettant de modifier une string de C# une fois celle-ci construite.
C'est parce que le type string de C# est un type « référence » qu'il est essentiel qu'une string soit immuable : du fait que les string sont systématiquement partagées plutôt que copiées, si une string de C# était mutable, il serait extrêmement difficile (pour ne pas dire impossible) de réaliser l'encapsulation dans ce langage.
Imaginez, pour comprendre cette subtilité, qu'une string de C# ne soit pas immuable, et expose une méthode setText() (ce n'est pas le cas; ceci n'est qu'une illustration) permettant de remplacer le texte qu'elle contient. Si tel était le cas, le code suivant :
// ...
string chevalDeTroie = "Bertrand";
Personne p = new Personne(chevalDeTroie);
chevalDeTroie.setText("Graziella"); // oups! Idem pour p.Nom.setText("...");
// ...
class Personne
{
public string Nom { get; private set; }
public Personne(string nom)
{
Nom = nom; // banal si string est immuable
}
}
... renommerait p à son insu. Cela compliquerait la programmation, c'est le moins qu'on puisse dire.
Le type string de C# expose une interface analogue à celle d'un tableau dont on ne pourrait pas modifier les éléments. À titre d'exemple, l'extrait de code suivant affichera chaque caractère de la chaîne s mais en intercalant un ' ' entre chacun d'eux, en utilisant l'expression s[i] pour accéder au i-ème élément de s et en utilisant s.Length pour connaître le nombre de caractères dans s :
// ...
string s = "J'aime mon prof";
for(int i = 0; i != s.Length - 1; ++i)
{
Console.Write($"{s[i]} ");
}
Console.WriteLine(s[s.Length-1]);
Notez toutefois que l'expression s[i]='A'; ne compilerait pas, une string étant immuable.
Il est aussi possible d'itérer à travers les éléments d'une string de C# à l'aide de foreach, car une string est énumérable :
// ...
string s = "J'aime mon prof";
foreach(char c in s)
{
Console.Write(c);
}
Le type string de C# n'expose pas de propriété ou de méthode d'instance permettant de savoir si une string donnée est vide. Il est tentant pour y arriver d'écrire ceci :
static bool EstVide(string s) => s.Length == 0; // risqué
... ou encore ceci :
static bool EstVide(string s) => s == string.Empty; // risqué, bien que operator==(string,string) soit surchargé
... ou encore ceci :
static bool EstVide(string s) => s == ""; // risqué, bien que operator==(string,string) soit surchargé
... car une référence nulle n'est pas la même chose qu'une référence vers une chaîne vide : si s est null, alors s.Length plantera et s == "" retournera false. L'usage en C# est plutôt d'écrire ceci :
// prudence : "".Equals(s) et Ok, mais pas pas s.Equals("") :
// "" n'est pas null, et que Equals(obj) retourne false si obj == null
static bool EstVide(string s) => "".Equals(s);
... ou encore cela :
static bool EstVide(string s) => string.IsNullOrEmpty(s); // même chose
Concaténer des string de C# est simple : il suffit d'utiliser l'opérateur + comme dans s = s0 + s1; ou encore l'opérateur += comme dans s0 += s1;. Notez qu'en C#, a += b est strictement équivalent à a = a + b; ce qui empêche des tas d'optimisations, mais ces optimisations ne s'appliqueraient pas sur un type immuable comme string de toute manière.
Puisque la concaténation sur des string est simple, mais string est immuable, cela signifie que chaque concaténation crée une nouvelle string (un new et une copie de caractères) silencieusement. C'est donc une opération très dispendieuse. Conséquemment, si vous souhaitez réaliser plusieurs concaténations, envisagez passer par un StringBuilder.
Le langage C# offre un sympathique mécanisme d'interpolation de chaînes de caractères, qui permet de construire une chaîne formatée à partir de l'évaluation d'expressions insérées à même le texte de la chaîne. Pour y avoir recours, il faut précéder le littéral string du symbole $ comme dans les exemples suivants (les expressions à évaluer sont placées entre accolades, et peuvent comprendre des guillemets) :
// ...
string qui = "mon chic prof";
string s0 = $"Allo {qui}!"; // "Allo mon chic prof!
int longueur = 3;
string s1 = $"La surface d'un carré de longueur {longueur} est {Math.Pow(longueur, 2)}"; // "La surface d'un carré de longueur 3 est 9"
string s2 = $"Une chaîne normale"; // "Une chaîne normale" telle quelle (pas d'expression à évaluer)
// ...
Cette notation accepte plusieurs codes de formatage, et le : joue un rôle dans certains de ces codes; pour cette raison, si vous souhaitez utiliser une expression ternaire à l'intérieur d'une chaîne interpolée, entourez l'expression de parenthèses :
// ...
int n = 3;
// Console.Write($"{n} est {n % 2 == 0? "pair" : "impair"}"); // non
Console.Write($"{n} est {(n % 2 == 0? "pair" : "impair")}"); // Ok
// ...
Certains symboles dans une chaîne de caractères demandent à être traités de manière particulière (voir https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#string-escape-sequences pour une liste). Par exemple, les guillemets doivent être précédés d'un \ pour distinguer le symbole affichable du code fermant la string, comme dans "allo \"toi\"" pour représenter le texte "allo "toi"" incluant les guillemets; certains métacaractères comme '\n' (saut de ligne), '\t' (tabulation), '\b' (backspace), etc. demandent une représentation particulière; et puisque le symbole \ sert pour marquer les métacaractères, il faut écrire '\\' ou "\\" pour représenter ce symbole en tant que tel.
Cela veut dire qu'une expression comme :
// ...
Console.Write("J'aime mon \"prof\"\n... de \\prog\\\nmalgré tout");
// ...
... affichera à la console ce qui suit :
J'aime mon "prof"
... de \prog\
malgré tout
Pour simplifier ces représentations, le langage C# supporte les chaînes verbatim, où la plupart des symboles sont pris tels quels. Ces chaînes sont précédées du symbole @, comme dans :
Console.Write(@"J'aime mon ""prof""
... de \prog\
malgré tout");
... qui affichera aussi à la console ce qui suit :
J'aime mon "prof"
... de \prog\
malgré tout
Notez que les guillemets ne sont pas pris tels quels et doivent être doublés ("") pour représenter le caractère guillemet (traditionnellement : '\"') dans une telle chaîne.
Il est possible d'avoir une chaîne verbatim interpolée en C#. Jusqu'à C# 8.0, il fallait insérer les préfixes dans l'ordre @$ pour ce faire, mais cette restriction est levée depuis. Par exemple :
// ...
string cours = "prog";
Console.Write(@$"J'aime mon ""prof""
... de \{cours}\
malgré tout");
// ...
Les services exposés pour la classe string de C# sont listés sur https://docs.microsoft.com/en-us/dotnet/api/system.string?view=netframework-4.8 mais quelques services clés méritent probablement un peu d'attention.
Il existe des constructeurs pour créer une string à partir d'un char[] comme à partir d'un char et d'un int, ce qui permet de simplifier plusieurs tâches courantes :
char [] cs = new char[]{ 'a', l', 'l', 'o' };
string s = new string(cs); // "allo"
s = new string('-', 10); // "----------"
Certaines opérations comme Insert, Join, Remove ou Replace opèrent sur une ou plusieurs string pour produire une autre string. Notez que, les string étant immuables, une fonction comme s.Replace('x','y') retourne une nouvelle string et laisse la chaîne originale intacte :
string s0 = "abc";
string s1 = s0.Replace('a', 'A');
Console.WriteLine($"s0 : {s0}, s1 : {s1}"); // s0 : abc, s1 : Abc
Des méthodes de conversion en majuscules (ToUpper) ou en minuscules (ToLower) sont offertes, de même que des fonctions pour élaguer des symboles aux extrémités (Trim, TrimStart, TrimEnd). Enfin, Split permet de séparer une string en un tableau de sous-chaînes, et Substring permet d'obtenir une sous-chaîne d'une chaîne donnée :
string s = "J'aime mon prof";
string [] mots = s.Split(); // par défaut, sépare aux blancs
foreach(string str in mots)
{
Console.WriteLine(str); // affiche un mot par ligne
}
Console.WriteLine(s.Substring(7, 3)); // mon
Quelques liens pour enrichir le propos.