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 :

// ...
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!
   }
}
class Program
{
   static void Main()
   {
     Nom id = new Nom("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
   }
}

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é :

using System;
using System.Text;

public class Program
{
   static (T, long) Tester<T>(Func<T> f)
   {
      var sw = new System.Diagnostics.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 StringBuilder();
      for (int i = 0; i < n; ++i)
      {
         sb.Append(s);
      }
      return sb.ToString();
   }
   public static void Main()
   {
      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");
      }
   }
}

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 !