Introduction : Pourquoi concaténer des chaînes n'est pas simplement "additionner"
\n\nImaginez : vous écrivez un service web en C# qui reçoit une liste de balises d'un utilisateur et doit les envoyer à une base de données sous forme d'une seule chaîne séparée par des virgules. Ou vous générez un rapport CSV pour l'exportation. Ou vous journalisez un ensemble d'identifiants. Une situation familière ? La question "Creating a comma separated list from IList
À première vue, quoi de plus simple ? Prenez une liste de chaînes, parcourez-la avec une boucle, ajoutez des virgules. Mais en pratique, cette tâche cache plusieurs pièges : des virgules superflues à la fin jusqu'à une chute catastrophique des performances sur de grands volumes de données. Dans cet article, nous ne nous contenterons pas de montrer des solutions toutes faites, nous examinerons sous le capot chacune d'elles. Vous découvrirez comment fonctionnent string.Join, StringBuilder et même LINQ Aggregate, et vous apprendrez à choisir le bon outil pour votre tâche.
C'est parti !
\n\nLe problème : Une requête simple, des complexités inattendues
\n\nSupposons que nous ayons une collection de chaînes :
\n\nIEnumerable<string> fruits = new List<string> { "Apple", "Banana", "Cherry", "Date" };\n\nNous voulons obtenir : "Apple,Banana,Cherry,Date". La première idée qui vient à un débutant est d'écrire une boucle :
string result = "";\nbool first = true;\nforeach (var fruit in fruits)\n{\n if (!first)\n result += ",";\n result += fruit;\n first = false;\n}\n\nCe code fonctionne, mais il le fait très mal. Pourquoi ? Parce que les chaînes en C# sont immuables. Chaque opération += crée une nouvelle chaîne en mémoire, en y copiant le contenu de la précédente. Avec 4 éléments, c'est imperceptible, mais avec 10 000 chaînes, vous obtenez une complexité quadratique O(n²) et une consommation mémoire gigantesque. Sous le capot, ce sont des milliers d'allocations et de collectes de déchets.
De toute évidence, une approche plus intelligente est nécessaire. Et c'est là qu'entrent en scène les protagonistes principaux.
\n\nMéthode 1 : string.Join — le roi des performances
\n\nLa manière la plus correcte et la plus performante est d'utiliser la méthode statique string.Join :
string csv = string.Join(",", fruits);\nConsole.WriteLine(csv); // Apple,Banana,Cherry,Date\n\nUne ligne de code et le tour est joué. Regardons sous le capot de string.Join (dans .NET Core / .NET 5+).
Que se passe-t-il à l'intérieur ?
\n- \n
- Calcul de la longueur : La méthode parcourt d'abord toute la collection (si c'est un
IEnumerable<T>) pour connaître le nombre total de caractères. Si la collection est un tableau ou unIList, elle obtient directement la longueur viaCount. \n - Allocation du tampon : Un grand tableau de caractères (
char[]) de la taille nécessaire est alloué : somme des longueurs de toutes les chaînes + (nombre de chaînes - 1) * longueur du séparateur. \n - Copie : Toutes les chaînes et les séparateurs sont copiés dans ce tampon sans une seule allocation superflue. \n
- Création de la chaîne : Une chaîne finale unique est créée à partir du tampon. \n
Résultat : une allocation mémoire (pour la chaîne finale) + allocation du tampon temporaire. Aucune chaîne intermédiaire. Complexité — strictement O(n).
\n\nPour IList<string>, la méthode fonctionne encore plus rapidement, car elle peut obtenir immédiatement Count et la longueur de chaque élément via l'indexeur, sans appeler GetEnumerator.
Méthode 2 : StringBuilder — un vieil ami
\n\nAvant l'apparition de string.Join optimisé (ou si vous êtes bloqué sur une ancienne version de .NET Framework), la solution standard était StringBuilder :
var sb = new StringBuilder();\nbool first = true;\nforeach (var fruit in fruits)\n{\n if (!first)\n sb.Append(',');\n sb