C# string ou StringBuilder

Dans les langages où il n'y a pas de réel accès direct aux objets, comme C# et Java par exemple (mais pas C++, qui offre de réels objets et permet de leur accéder directement), certains types essentiels doivent être immuables. Par immuable, on entend un type dont les instances ne peuvent être modifiées une fois construites. Cela est essentiel pour assurer l'encapsulation dans ces langages, d'ailleurs.

Par exemple, supposez les classes Nom et Personne ci-dessous :

// ...
Nom id = new ("Bertrand");
var p = new Personne(id); // p se nomme Bertrand... pour le moment
id.Valeur = "Graziella"; // oups! p.Identification et id réfèrent au même objet;
                         // il est difficile de réaliser l'encapsulation dans un
                         // langage où les objets sont partagés par défaut...

Console.WriteLine($"Mon nom est {p.Identification]"); // Mon nom est Graziella
class Nom
{
   public string Valeur { get; set; } // set public, pas immuable
   public int Nom(string valeur)
   {
      Valeur = valeur;
   }
}
class Personne
{
   public Nom Identification { get; private set; }  // set privé, mais Nom n'est pas immuable; aucune encapsulation possible en C#
   public Personne(Nom identification)
   {
      Identification = identification; // oups! cheval de Troie!
   }
}

Essentiel? Pensez-y : la classe object (System.Object) expose une méthode virtuelle ToString retournant une string... Le type string est littéralement partout dans ce langage!

Puisque ce modèle où tout est manipulé par référence (donc partagé) rend les programmes extrêmement fragiles et fait en sorte que l'encapsulation est très difficile à réaliser, il est d'usage de maximiser le recours aux classes immuables. Puisque string (donc System.String) est un type essentiel en C#, il n'est pas surprenant que ce type soit immuable.

Cela entraîne toutefois un coût important pour ce qui est de la vitesse d'exécution; ce coût se manifeste de manière très visible dans la concaténation de chaînes de caractères.

En effet, le code suivant :

string s = "J'aime";
s += " mon";
s += " prof";

... implique créer, pour chaque appel à l'opérateur +=, une nouvelle chaîne contenant la concaténation des deux chaînes impliquées (donc un new) puis de copier tous les caractères des deux chaînes dans cette nouvelle chaîne, ce qui est relativement dispendieux. On ne le voit pas vraiment sur une opération, mais sur plusieurs...

Pour cette raison, les langages comme C# et Java offrent typiquement deux classes pour manipuler des chaînes de caractères :

À titre d'exemple, voici un programme concaténant plusieurs instances d'une chaîne de caractères en C#, avec un exemple utilisant string et un autre utilisant StringBuilder. Les deux fonctionnent et donnent la même string une fois le calcul complété (voir https://dotnetfiddle.net/5HVZ6G mais notez que j'ai « enlevé un zéro » aux tests sinon ceux-ci deviennent trop longs pour cet outil Web) :

using System;
using System.Text;
using System.Diagnostics;

var (r0, dt0) = Tester(() => ConcaténerString("J'aime mon prof", 100_000));
var (r1, dt1) = Tester(() => ConcaténerStringBuilder("J'aime mon prof", 100_000));
if (r0 != r1)
{
   Console.WriteLine("Quelque chose de suspect s'est passé");
}
else
{
   Console.WriteLine($"string : {dt0} ms; StringBuilder : {dt1} ms");
}

static (T, long) Tester<T>(Func<T> f)
{
   var sw = new Stopwatch();
   sw.Start();
   T rés = f();
   sw.Stop();
   return (rés, sw.ElapsedMilliseconds);
}
static string ConcaténerString(string s, int n)
{
   string rés = "";
   for (int i = 0; i < n; ++i)
   {
      rés += s;
   }
   return rés;
}
static string ConcaténerStringBuilder(string s, int n)
{
   StringBuilder sb = new ();
   for (int i = 0; i < n; ++i)
   {
      sb.Append(s);
   }
   return sb.ToString();
}

Toutefois, le résultat affiché est :

string : 56390 ms; StringBuilder : 20 ms

... ce qui montre clairement que les coûts impliqués ne sont absolument pas mineurs.


Valid XHTML 1.0 Transitional

CSS Valide !