Aller au contenu

Améliorer les performances grâce à ReadOnlySpan<T>

Introduction

Apparu dans la version 2.1 de .NET Core, le type ReadOnlySpan<T> permet de manipuler des données en mémoire de manière performante, sans réaliser de copie des données à manipuler. Contrairement aux approches classiques reposant sur les méthodes Substring() ou Split(), il propose une vue en lecture seule sur une zone mémoire existante.

Pourquoi les performances sont-elles améliorées ? Certes, parce qu'il n'y a pas de copie de données. Mais aussi parce que les allocations mémoire sont réduites et le Garbage Collector a moins de travail à réaliser lors de ses passages.

Contraintes d'utilisation

Le type ReadOnlySpan<T> est un ref struct, c'est-à-dire une structure qui ne peut exister que dans la pile d'exécution (stack). Contrairement aux structures classiques, elle ne peut pas être allouée sur le tas (heap) ni être utilisée dans certains contextes. Ainsi une variable de ce type ne peut pas être :

  • Stocké dans un attribut d'une classe

  • Capturé par une expression lambda, car elle peut être exécutée après la fin du bloc de code dans lequel le span a été créé. Le compilateur interdit donc cette situation afin de garantir la sécurité mémoire.

  • Conservé pendant une opération asynchrone (await)

Ces restrictions garantissent que le span ne référence jamais une zone mémoire devenue invalide.

Démonstration par l'exemple

Lorsque nous écrivons les instructions suivantes afin d'obtenir l'année, dans une date définie dans une chaîne de caractères :

string date = "2026-06-30";
string year = date.Substring(0, 4);

Ce code est opérationnel. Cependant, la méthode Substring() crée une nouvelle instance de string contenant les quatre premiers caractères, ce qui entraîne une allocation mémoire supplémentaire.

En revanche, si nous écrivons :

ReadOnlySpan<char> year = date.AsSpan(0, 4);

Alors la variable year représente une vue sur les quatre premiers caractères de la chaîne d'origine.

En écrivant ces instructions, nous obtenons directement le jour, le mois et l'année de manière performante :

ReadOnlySpan<char> date = "2026-06-30";

int year = int.Parse(date[..4]);
int month = int.Parse(date.Slice(5,2));
int day = int.Parse(date.Slice(8,2));

Découpage d'une chaîne de caractères

Soit la chaîne de caractères suivante :

var line = "James;RAVAILLE;.NET;C#";

Pour découper cette chaîne de caractères, le type string propose la méthode Split :

var parts = line.Split(';');

Un tableau avec 4 chaînes de caractères est créé après les allocations mémoire nécessaires. En écrivant :

int separator = line.IndexOf(';');
ReadOnlySpan<char> firstName = line[..separator];

Nous accédons au prénom de manière performante, sans allocation mémoire.

Accès aux données dans un tableau

Voici un tableau de nombres :

int[] values = [6, 9, 19, 79];

Voici comment accéder aux deux premiers éléments du tableau :

ReadOnlySpan<int> span = values.AsSpan(0, 2);

On peut parcourir les données pointées par la variable span :

foreach (var value in span)
{
    ...
}

Si nous accédons à des éléments (ou des caractères) avec des valeurs qui sortent de la plage de valeurs autorisée :

ReadOnlySpan<int> span = values.AsSpan(0, 200);

Alors une exception de type ArgumentOutOfRangeException est levée.