Dot.Blog

Consulting DotNet C#, XAML, WinUI, WPF, MAUI, IA

Comprendre et maîtriser Span<T> et la mémoire stack en .NET moderne

Dans l’univers de .NET 9, les performances sont au cœur des préoccupations. Pour les développeurs avancés, l’un des outils les plus puissants mais souvent mal compris reste Span<T>. Cet article vous propose une introduction concrète, sans jargon inutile, pour comprendre ce type structuré sur la stack et apprendre à l’utiliser efficacement.

🔍 Pourquoi Span<T> ?

En .NET, les tableaux et les chaînes de caractères sont souvent manipulés via des allocations sur le heap. Cela peut rapidement devenir un goulet d’étranglement, surtout dans des boucles ou traitements intensifs.

Span<T> permet de manipuler des données (tableaux, portions de mémoire, buffers) sans allocation supplémentaire et sans copie. Il s’agit d’un type ref struct, ce qui signifie :

  • Il vit sur la stack, non sur le heap
  • Il ne peut pas être boxé
  • Il ne peut pas sortir de la portée de la méthode

🧪 Exemple simple : trancher un tableau sans le copier

int[] numbers = Enumerable.Range(0, 100).ToArray();
Span<int> middle = new Span<int>(numbers, 40, 10);

for (int i = 0; i < middle.Length; i++)
    middle[i] *= 2;

Console.WriteLine(string.Join(", ", numbers.Skip(40).Take(10)));

Ici, middle référence une portion du tableau sans le copier. Toute modification dans middle affecte directement numbers.

✂️ Avec des chaînes de caractères : ReadOnlySpan<char>

Span<T> ne fonctionne pas directement avec les chaînes (car elles sont immuables), mais ReadOnlySpan<char> est l’outil idéal pour analyser du texte sans l’allouer plusieurs fois.

string input = "[clé]=[valeur]";
ReadOnlySpan<char> span = input;

int sep = span.IndexOf('=');
ReadOnlySpan<char> key = span.Slice(1, sep - 2);
ReadOnlySpan<char> value = span.Slice(sep + 2, span.Length - sep - 3);

Console.WriteLine($"Clé : {key}, Valeur : {value}");

⚠️ Cela évite l’allocation de chaînes intermédiaires via Substring().

💡 Lire un fichier ligne à ligne en mémoire avec Span<byte>

byte[] buffer = File.ReadAllBytes("data.txt");
Span<byte> span = buffer;
while (!span.IsEmpty)
{
    int i = span.IndexOf((byte)'\n');
    Span<byte> line = span.Slice(0, i);
    Console.WriteLine(System.Text.Encoding.UTF8.GetString(line));
    span = span.Slice(i + 1);
}
Ici, chaque ligne est extraite sans allouer une nouvelle portion mémoire à chaque fois. Idéal pour traiter rapidement des fichiers.

🚫 Ce qu’on ne peut pas faire avec Span<T>

  • Le stocker dans un champ d’un objet
  • Le capturer dans un lambda asynchrone
  • Le passer entre threads ou tâches
  • Le convertir implicitement vers object

Ce sont des contraintes liées à sa nature stack-only, mais elles protègent contre des erreurs subtiles.

✅ Bonnes pratiques

  • Utiliser Span<T> pour trancher/accéder à des buffers, fichiers, tableaux.
  • Utiliser ReadOnlySpan<char> pour parser efficacement une chaîne.
  • Préférer Span<T> à Substring, Array.Copy, ou ToArray() si on reste dans une méthode synchrone courte.

🔚 Conclusion

Span<T> permet d’écrire du code .NET plus rapide et plus sûr, mais demande une bonne compréhension de la stack. Pour tout traitement local de données (parsing, transformation, mémoire tampon), c’est un outil précieux.

Il s’intègre parfaitement à la philosophie de .NET 9 : plus de performance, moins d’allocation.

Ce sont des petites choses comme Span<T> qui peuvent améliorer grandement une App où les temps sont serrés et la mémoire trop sollicitées. Ce n'est pas une solution universelle, elle a ses inconvénients, mais gardez-la en tête pour y penser lorsque cela s'imposera...

Stay Tuned !

Faites des heureux, PARTAGEZ l'article !