Show this page in english

Classes de base : Gestion des chaînes de caractères

Publié le 4 Janvier 2007

1. Introduction

Qui n'a jamais utilisé une chaîne de caractère dans son programme via le type string ? Etant donné que cela est obligatoire si l'on veut communiquer un minimum d'informations à l'utilisateur, je ne vous demande pas la réponse...

Mais savez vous en réalité ce que vous manipulez et la puissance que ces objets ont ?

Lorsque vous créez une chaîne de caractères via le type string, vous instanciez en réalité une classe System.String possédant de nombreuses méthodes très utiles.

Nous étudierons donc cette classe String puis verrons comment manipuler judicieusement les chaînes de caractères et nous terminerons par le formatage de chaînes...

2. System.String

System.String est une classe fondamentale de .NET. Elle est exclusivement conçue pour stocker des chaînes de caractères et met à disposition du programmeur un grand nombre d'opérations de traitement des chaînes

En raison de son importance, il existe un certain nombre de raccourcis facilitant ainsi le développement.

2.1 Les raccourcis

Vous en connaissez certainement quelques un...

Le type string :

String s = new String("coucou"); //équivaut à string s = "coucou";

La concaténation :

string msg1 = "Hello";
string msg2 = " World"; msg2 = String.Concat(msg1, msg2);
//équivaut à msg2 += msg1;

L'extraction de caractère (syntaxe d'indexage) :

char c = msg[4]; //5e caractère de la chaîne

Création d'une chaîne vide :

string s = String.Empty; //équivaut à string s = "";

La comparaison :

string s1 = "hello"; string s2 = "hello"; bool c1 = s1.Equals(s2); //équivaut à bool c1 = (s1 == s2);

Test d'existence :

bool b = (s == null) || (s == ""); //équivaut à bool b = String.IsNullOrEmpty(s);

2.2 Les méthodes

Un grand nombre de méthodes assurent des tâches communes, telles que remplacer des caractères, retirer des espaces, transformer des minuscules en majuscules...

Les principales méthodes statiques :

Les méthodes non statiques :

2.3 Taille d'une chaîne de caractères

Pour connaître la taille d'une chaîne de caractères, il suffit d'utiliser la propriété Length de la chaîne en question.

3. Construction de chaînes

3.1 Fonctionnement sous-jacent du type string

Bien que string soit une classe très puissante et très souple, offrant un grand nombre de méthode très utiles, cette classe s'avère peut efficace voire très lourde lorsqu'il s'agit d'effectuer beaucoup d'opérations de modifications d'une chaîne de caractères. En effet, string est un type de données immuable : une fois initialisé, celui-ci ne peut jamais changer de valeur.

Les méthodes qui opèrent sur les chaînes de caractères, bien qu'elles semblent en modifier le contenu, créent en réalité une nouvelle chaîne pour y copier l'ancienne et y effectuer les opérations requises.

Par exemple, considérez le petite morceau de code suivant :

string s = "Hello"; s += " World";

Que se passe-t-il ici ?

A priori, on effectue ici deux opérations : on crée la chaîne "Hello" puis on y ajoute " World"...

En fait, on compte au moins cinq opérations :

"C'est tout" me direz vous ! Eh bien oui, mais çà fait déjà beaucoup, copier une chaîne de caractères dans une autre, créer de nouveaux objets... tout cela consomme des ressources et si vous devez effectuer un millier de fois ce type d'opérations sur des chaînes bien plus longues, je peux vous assurer que vous allez devenir tout à coup très pointilleux sur les performances de votre programme.

Exemple :

On veut transformer le texte suivant : "Lorem Ipsum dolor Sit amet, Consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore Magna aliqua. Ut enim ad minim Veniam, quis nostrud exercitation ullamco laboris nisi ut Aliquip ex ea commodo consequat. Duis aute irure dolor in Reprehenderit in voluptate velit esse cillum dolore eu Fugiat nulla pariatur. Excepteur sint Occaecat cupidatat non Proident, sunt in culpa qui officia deserunt mollit anim id est laborum." qui comporte 440 caractères. La transformation a pour but de remplacer chaque caractère par le caractère qui le suit dans les tables ASCII. La méthode la plus simple pour y parvenir est d'utiliser la méthode Replace. Voici le code :

using System;

public class Program { public static void Main() { string text = "Lorem Ipsum dolor Sit amet, Consectetur " + "adipisicing elit, sed do eiusmod tempor incididunt ut" + "labore et dolore Magna aliqua. Ut enim ad minim Veniam," + "quis nostrud exercitation ullamco laboris nisi ut Aliquip" + "ex ea commodo consequat. Duis aute irure dolor in Reprehenderit" + "in voluptate velit esse cillum dolore eu Fugiat nulla pariatur." + "Excepteur sint Occaecat cupidatat non Proident, sunt in culpa qui" + "officia deserunt mollit anim id est laborum."; Console.WriteLine("Texte : " + text); Console.WriteLine("Taille : " + text.Length); Console.WriteLine("Encryption..."); //On commence l'étude ici for (int i = (int)'a'; i <= (int)'z'; i++) { char oldChar = (char)i; //Attention : ++i est différent de i++ //Rappel : ++i équivaut à i+1 alors que i++ équivaut à i += 1 char newChar = (char)(++i); text = text.Replace(oldChar, newChar); } for (int i = (int)'A'; i <= (int)'Z'; i++) {
char oldChar = (char)i; char newChar = (char)(++i); text = text.Replace(oldChar, newChar); } //On termine l'étude là Console.WriteLine("Result : " + text); Console.ReadLine(); } }

Je vous laisse admirer le résultat. Vous noterez que la ponctuation n'a pas été modifiée (c'est normal bien entendu).

Remarque : La chaîne originale (lipsum.com) a été modifié (ajout de majuscules) afin d'utiliser un peu plus la seconde boucle...

Lorsque vous exécutez le programme, la transformation est bien entendue instantannée (ou changez de PC sinon). Et heureusement d'ailleurs car ce programme ne fait pas grand chose. Mais voyons un peu la consommation en mémoire...

La chaîne originale comporte 21 minuscules et 14 majuscules. La première boucle va donc effectuer 21 Replace. On va donc créer 21 chaînes de caractères dans cette boucle. La seconde boucle va effectuer 14 Replace et créer ainsi 14 chaînes supplémentaires. Chaque chaîne étant composée de 440 caractères et la chaîne étant encodée en ASCII (un caractère fait un octet en ASCII), nous allons donc utiliser ici 440x35, soit 15400 octets de mémoire juste pour ces modifications et si l'on ajoute la création de la chaîne originale, cela fait 15840 octets. Si votre chaîne est encodée en Unicode, la consommation est doublée (un caractère Unicode fait 2 octets).

Traiter beaucoup de texte avec le type string s'avère donc couteux en terme de ressources et donc de performances.

3.2 StringBuilder

Pour pallier à ce problème, Microsoft a implémenté une classe très pratique : StringBuilder qui se trouve dans l'espace de nom System.Text.

StringBuilder fournit quelques méthodes de la classe String, comme Replace et Insert par exemple, mais possède beaucoup moins de méthodes.

Cette classe est bien plus efficace que string pour manipuler des chaînes car elle permet d'allouer un espace mémoire à une chaîne de caractères. Cet espace est toujours plus grand que la chaîne elle-même.

//Un constructeur de StringBuilder public StringBuilder(int capacity, int maxCapacity);

On peut alors ajouter autant de sous-chaînes que l'on veut sans avoir besoin de recréer à chaque fois un objet StringBuiler... tant que l'espace alloué est suffisant. Si celui-ci devient trop faible, un supplément de mémoire est affecté à votre builder et le contenu est copié.

Exemple :

//Ne l'oubliez pas using System.Text; //Dans le code... StringBuilder sb = new StringBuilder("Lorem Ipsum dolor Sit amet, Consectetur ", 440); sb.Append("adipisicing elit, sed do eiusmod tempor incididunt ut"); sb.Append("labore et dolore Magna aliqua. Ut enim ad minim Veniam,"); sb.Append("quis nostrud exercitation ullamco laboris nisi ut Aliquip"); sb.Append("ex ea commodo consequat. Duis aute irure dolor in Reprehenderit"); sb.Append("in voluptate velit esse cillum dolore eu Fugiat nulla pariatur."); sb.Append("Excepteur sint Occaecat cupidatat non Proident, sunt in culpa qui"); sb.Append("officia deserunt mollit anim id est laborum.");

3.3 Note (source MSDN) :

Cette classe représente un objet qui ressemble à une chaîne et dont la valeur est une séquence de caractères mutables. Cette valeur est dite mutable parce qu'elle peut être modifiée après sa création par ajout, suppression, remplacement ou insertion de caractères. Pour comparer, consultez la classe String.

La plupart des méthodes qui modifient une instance de cette classe retournent une référence à cette même instance. Dans la mesure où une référence à l'instance est retournée, vous pouvez appeler une méthode ou une propriété sur cette référence. Cela peut s'avérer pratique si vous voulez écrire une seule instruction qui enchaîne des opérations successives.

La capacité de StringBuilder est le nombre maximal de caractères que l'instance peut stocker à un moment donné ; ce nombre est supérieur ou égal à la longueur de la chaîne qui représente la valeur de l'instance. Il est possible d'augmenter ou de diminuer la capacité à l'aide de la propriété Capacity ou de la méthode EnsureCapacity, mais elle ne peut pas être inférieure à la valeur de la propriété Length.

Les valeurs par défaut spécifiques à l'implémentation sont utilisées si aucune capacité ou capacité maximale n'est spécifiée lors de l'initialisation d'une instance de StringBuilder.

Considérations sur les performances

Les méthodes Concat et AppendFormat concatènent toutes deux les nouvelles données en un objet String ou StringBuilder existant. Une concaténation d'objet String crée toujours un nouvel objet à partir de la chaîne existante et des nouvelles données. Un objet StringBuilder gère une mémoire tampon qui contient la concaténation des nouvelles données. Les nouvelles données sont ajoutées à la fin de la mémoire tampon, si l'espace nécessaire est disponible ; sinon, une nouvelle mémoire tampon, plus grande, est allouée, les données de la mémoire tampon d'origine sont copiées dans la nouvelle mémoire tampon, et les nouvelles données sont ajoutées à la nouvelle mémoire tampon.

La performance d'une opération de concaténation pour un objet String ou StringBuilder dépend de la fréquence à laquelle une allocation de mémoire a lieu. Une opération de concaténation String alloue toujours la mémoire, alors qu'une opération de concaténation StringBuilder alloue seulement la mémoire si la mémoire tampon de l'objet StringBuilder est trop petite pour contenir les nouvelles données. Par conséquent, la classe String est préférable pour une opération de concaténation si un nombre fixe d'objets String est concaténé. Dans ce cas, les opérations de concaténation individuelles peuvent même être combinées en une seule opération par le compilateur. Un objet StringBuilder est préférable pour une opération de concaténation si un nombre arbitraire de chaînes est concaténé; par exemple, si une boucle concatène un nombre aléatoire de chaînes d'entrées d'utilisateur.

Remarques à l'attention des implémenteurs

La capacité par défaut pour cette implémentation est 16 et la capacité maximale par défaut est Int32.MaxValue. Si nécessaire, StringBuilder peut allouer plus de mémoire pour stocker les caractères lorsque la valeur d'une instance est augmentée et la capacité est modifiée en conséquence. La quantité de mémoire allouée est spécifique à l'implémentation et le système lève ArgumentOutOfRangeException si la quantité de mémoire requise est supérieure à la capacité maximale. Par exemple, les méthodes Append, AppendFormat, EnsureCapacity, Insert et Replace peuvent augmenter la valeur d'une instance. Les caractères dans la valeur de StringBuilder sont accessibles à l'aide de la propriété Chars. Les positions d'index commencent à zéro.

3.4 Exemple

Si on reprend le dernier exemple, mais avec un StringBuilder, cela donne ceci :

using System; using System.Text; public class Program { public static void Main() { StringBuilder sb = new StringBuilder("Lorem Ipsum dolor Sit amet, Consectetur " + "adipisicing elit, sed do eiusmod tempor incididunt ut" + "labore et dolore Magna aliqua. Ut enim ad minim Veniam," + "quis nostrud exercitation ullamco laboris nisi ut Aliquip" + "ex ea commodo consequat. Duis aute irure dolor in Reprehenderit" + "in voluptate velit esse cillum dolore eu Fugiat nulla pariatur." + "Excepteur sint Occaecat cupidatat non Proident, sunt in culpa qui" + "officia deserunt mollit anim id est laborum."); //sb n'est pas un type string, si on veut l'afficher, il faut le transformer en string Console.WriteLine("Texte : " + sb.ToString()); Console.WriteLine("Taille : " + sb.Length); Console.WriteLine("Capacité : " + sb.Capacity); Console.WriteLine("Capacité max : " + sb.MaxCapacity); Console.WriteLine("Encryption..."); //On commence l'étude ici for (int i = (int)'a'; i <= (int)'z'; i++) { char oldChar = (char)i; //Attention : ++i est différent de i++ //Rappel : ++i équivaut à i+1 alors que i++ équivaut à i += 1 char newChar = (char)(++i); sb = sb.Replace(oldChar, newChar); } for (int i = (int)'A'; i <= (int)'Z'; i++) { char oldChar = (char)i; char newChar = (char)(++i); sb = sb.Replace(oldChar, newChar); } //On termine l'étude là /* mais il existe un transtypage implicite depuis .NET 2.0 qui permet de transformer * un StringBuilder en un type String */ Console.WriteLine("Result : " + sb); Console.ReadLine(); } }

Avec ce code, au lieu de mobiliser plus de 15 Ko de mémoire, on utilise seulement 440 octets (512 en réalité car il s'agira ici de la capacité), ce qui correspond à la taille de la chaîne originale. Avouez que c'est quand même beaucoup mieux...

3.5 Quand utiliser String ou StringBuilder ?

De manière générale, on peut dire qu'il est préférable d'utiliser StringBuilder pour manipuler des chaînes et String pour ranger ou afficher le résultat final.

4. Formatage des chaînes

4.1 Qu'est ce que c'est ?

Formater une chaîne signifie la "mettre en forme".

Par exemple, vous voulez afficher une date. Plusieurs possibilités s'offrent à vous : l'afficher au format anglais (24/12/06), américan (12/24/06), allemand (24. December 2006), etc. De même pour les nombres, vous pouvez afficher un nombre au format scientifique, au format héxadécimal, etc.

Vous avez certainement déjà formaté des chaînes, même si vous ne le saviez pas :

double p = 3.14; int a = 2007; Console.Write("Pi vaut environ : {0} Cet article a été publié en {1}", p, a);

A part les méthodes d'affichage de la console, vous n'avez pas accès directement au formatage de chaîne. Si vous voulez, par exemple, formater une chaîne pour ensuite l'ajouter à un TreeView, ListView, TextBox ou autre composant, vous devez utiliser la méthode statique Format de la classe String. Ainsi le code précédent peut s'écrire :

double p = 3.14; int a = 2007; Console.Write(String.Format("Pi vaut environ : {0} Cet article a été publié en {1}", p, a));

Mais dans ce cas là, cette écriture n'a pas d'interêt.

ToString peut également jouer le rôle de String.Format (voir exemple au paragraphe 5).

4.2 Indice, nombre, spécificateur

Il s'agit des trois composantes à placer entre accolades, les deux dernières étant facultatives.

Il existe un grand nombre de spécificateurs.

Ci-dessous, la liste complète des spécificateurs standards.

Spécificateurs de format numériques standard
Spécificateur Signification Exemple
C ou c Valeur monétaire spécifique à la localisation $4999.90 (format américain)
£4999.90 (format anglais)
D ou d Entier général 4999
E ou e Notation scientifique 4.999E+003
F ou f Nombre décimal à virgule fixe 4999.90
G ou g Nombre général 4999.9
N ou n Format général spécifique à la localisation pour les nombres 4,999.90 (formats anglais et américain)
4 999,90 (format de l'Europe continentale)
P ou p Notation en pourcentage 499 990,00 %
R ou r Comme G, mais avec une précision accrue de 2 chiffres. Valable sur les types à virgule flottante uniquement. 0.1234567890123456789 devient
avec G : 0.1234567890123456
avec R : 0.123456789012345678
X ou x Format hexadécimal 1387 (pour afficher 0x1387, vous devez écrire 0x séparement)

Note : Si vous voulez qu'un entier soit complété par des zéros, vous pouvez utiliser le spécificateur de format 0 (zéro) répété le nombre de fois voulu. Par exemple, le spécificateur de format 0000 affiche 0003 pour 3 et 0099 pour 99...

Spécificateurs de format DateTime standard
Spécificateur Signification Exemple
d Modèle de date courte 04/01/2007
D Modèle de date longue jeudi 4 janvier 2007
t Modèle d'heure abrégée 19:54
T Modèle d'heure longue 19:54:49
f Modèle de date/heure complet (heure abrégée) jeudi 4 janvier 2007 19:54
F Modèle de date/heure complet (heure longue) jeudi 4 janvier 2007 19:54:49
g Modèle de date/heure général (heure abrégée) 04/01/2007 19:54
G Modèle de date/heure général (heure longue) 04/01/2007 19:54:49
M ou m Modèle de mois-jour 4 janvier
R ou r Modèle standard RFC1123, indépendant de la culture (heure locale) Thu, 04 Jan 2007 19:54:49 GMT
s Modèle de date/heure pouvant être trié ; conforme à ISO 8601 2007-01-04T19:54:49
u Modèle de date/heure universel pouvant être trié 2007-01-04T19:54:49Z
U Modèle de date/heure universel pouvant être trié (heure GMT) jeudi 4 janvier 2007 18:54:49
Y ou y Modèle d'année-mois janvier 2007

Remarque : Il existe également des spécificateurs de format numériques personnalisés ainsi que des spécificateurs de format DateTime personnalisés.

4.3 Principe de formatage d'une chaîne

Exemple :

Console.Write("Pi vaut environ : {0,10:E}\nCet article a été publié en {1}", Math.PI, 2007);

Nous avons déjà dit que le code précédent était équivalent à :

Console.Write(String.Format("Pi vaut environ : {0,10:E}\nCet article a été publié en {1}", Math.PI, 2007));

Et en effet, c'est exactement ce qui se passe :

public virtual void Write(string format, object arg0, object arg1) { this.Write(string.Format(this.FormatProvider, format, new object[] { arg0, arg1 })); }

Remarques :

En fait, la méthode Write de la classe Console n'effectue pas directement cette opération. Celle-ci passe en effet par un TextWriter à travers l'une de ses propriétés statiques Out. C'est cette classe TextWriter qui contient la méthode Write décrite ci-dessus.

Le FormatProvider passé en paramètre est celui du thread en cours d'exécution et dépend de la culture de celui-ci.

Un petit morceau de code vaut mieux que de longs discours : voyons ce que contient la méthode statique String.Format...

public static string Format(IFormatProvider provider, string format, params object[] args) { if ((format == null) || (args == null)) throw new ArgumentNullException((format == null) ? "format" : "args"); StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8)); builder.AppendFormat(provider, format, args); return builder.ToString(); }

Rien de bien difficile jusque là. Notez l'utilisation d'un StringBuilder. En effet, la mise en forme d'une chaîne requiert de manipuler celle-ci et le nombre d'arguments pouvant être tout aussi bien de un ou deux que de deux milliards, l'utilisation de StringBuilder est tout à fait judicieuse ici.

En revanche, AppendFormat est bien plus longue (211 lignes pour être exact). Nous allons donc nous limiter à expliquer de son fonctionnement.

AppendFormat commence par découper la chaîne de caractères, en séparant les éléments entre crochets (tout en conservant leur ordre), du reste du texte. Le texte est ajouté à la chaîne en construction par la méthode Append de StringBuilder et les arguments sont analysés au fur et à mesure qu'ils doivent être ajouté à la chaîne... C'est cette analyse qui nous interesse.

Au moment de formater l'objet, AppendFormat vérifie si celui-ci implémente une interface : IFormattable. Cette vérification s'effectue à l'aide du mot clé is. On distingue alors deux possibilités :

Etant donné que tous les types numériques primaires implémentent cette interface, nous nous trouvons ici dans le second cas pour le double (condition vérifiée). La surcharge à deux paramètres de ToString est donc appellée. Le paramètre de format ("{0,10:E}" pour le double) ainsi que la culture sont passés à cette méthode qui gère à présent le processus de mise en forme de l'objet selon son implémentation.

Le deuxième paramètre est un entier (int) mais celui-ci n'a pas besoin d'être formaté (non demandé), l'argument string format a donc une valeur null. Du fait de cette valeur, la méthode ToString() (sans paramètre) est appellée pour décrire l'entier sous forme d'une chaîne de caractères.

5. Exemple

L'exemple suivant met en pratique tout ce qui a été dit précédemment. Comprenez le et vous saurez formater convenablement vos chaînes.

using System; using System.Text; public class Program { public static void Main() { Vecteur3D[] vecteurs = new Vecteur3D[3]{ new Vecteur3D(1.0,3.5,-2.1), new Vecteur3D(0.0,-5.0,1.3), new Vecteur3D(4.2, 0.0, 7.8) }; Console.WriteLine("Affichage standard des vecteurs :"); foreach (Vecteur3D v in vecteurs) Console.WriteLine(v); Console.WriteLine("\nAffichage personnalisé des vecteurs :"); Console.WriteLine("-> Format EXP :");
foreach (Vecteur3D v in vecteurs) Console.WriteLine("---> {0:EXP}",v); Console.WriteLine("-> Format IJK :"); foreach (Vecteur3D v in vecteurs) Console.WriteLine("---> {0:IJK}", v); Console.WriteLine("-> Format NORM :"); foreach (Vecteur3D v in vecteurs) Console.WriteLine("---> {0:NORM}", v); Console.WriteLine("-> Format Z :"); foreach (Vecteur3D v in vecteurs) Console.WriteLine("---> {0:Z}", v); Console.ReadKey(); } } public struct Vecteur3D : IFormattable { private double x, y, z; public Vecteur3D(double x, double y, double z) { this.x = x; this.y = y; this.z = z; } //... propriétés renvoyant X, Y et Z ...
//Méthodes public double Norm() { return Math.Sqrt(x * x + y * y + z * z); } public string ToString(string format, IFormatProvider provider) { if (format == null) return ToString(); switch (format.ToUpper()) { case "EXP": /* Il ne s'agit pas ici de la méthode la plus judicieuse de construction, * mais d'un exemple de l'utilisation du spécificateur de format de ToString * pour les types numériques. */ return "{ " + x.ToString("E") + " ; " + y.ToString("E") + " ; " + z.ToString("E") + "}"; case "IJK": return String.Format("{0:N}*i + {1:N}*j + {2:N}*z", x, y, z); case "NORM": StringBuilder sb = new StringBuilder(16);
sb.Append("|| "); sb.AppendFormat("{0,-10:N}", Norm()); sb.Append(" ||"); return sb.ToString(); default: return ToString(); } } public override string ToString() { return String.Format("({0} ; {1} ; {2})", x, y, z); } }