CodeGym /Cours /C# SELF /Memory<T> et ...

Memory<T> et ReadOnlyMemory<T>

C# SELF
Niveau 65 , Leçon 4
Disponible

1. Introduction

Tout a commencé parce que les devs .NET voulaient accélérer le travail avec de gros volumes de données et permettre aux développeurs de le faire de façon pratique et sûre. D’abord est apparu Span<T>, qui est une "fenêtre" sur un bloc contigu de mémoire, capable de montrer une partie d’un tableau, d’une chaîne ou même de mémoire allouée en dehors de .NET (par ex. via P/Invoke).

Cependant Span<T> a une contrainte puissante : il doit toujours vivre sur la pile. On ne peut pas le stocker dans des champs de classe, le retourner depuis des méthodes ou le passer entre des méthodes asynchrones. La raison — sécurité : si quelqu’un garde une référence vers une mémoire qui n’existe plus, l’application plante.

Parfois il faut retourner des slices depuis des méthodes, les garder dans des collections ou des champs de classes, et aussi les utiliser dans des API asynchrones. C’est là que Memory<T> entre en jeu — en gros, c’est la version "longue durée" et sûre de Span<T>, qui peut vivre sur le heap, être passée entre threads, exister dans des propriétés et des objets et se comporter comme un objet .NET normal.

Il y a aussi un "grand frère" — ReadOnlyMemory<T>, qui, comme on l’imagine, n’autorise pas la modification des données sous-jacentes, mais permet de les lire partout où c’est nécessaire.

2. Quelle est la différence entre Span<T> et Memory<T>

Voici un petit tableau pour comparer visuellement :

Span<T>
Memory<T>
Où il vit Seulement sur la pile (stack only) Sur la pile et sur le tas (heap/stack)
Peut être stocké dans un champ ❌ Non ✅ Oui
Peut être retourné depuis une méthode ❌ Non ✅ Oui
Méthodes asynchrones/await ❌ Interdit ✅ Possible
Modifiable ✅ Il existe aussi ReadOnlySpan<T> ✅ Il existe aussi ReadOnlyMemory<T>
Permet de slicer des données ✅ Oui ✅ Oui

Si vous devez parcourir rapidement des données à l’intérieur d’une méthode — prenez Span<T>. Si vous devez renvoyer le résultat à l’extérieur ou le mettre dans un champ de classe — utilisez Memory<T>. Et si les données sont en lecture seule — prenez ReadOnlyMemory<T>.

3. Signature et structure de base de Memory<T>

Comme d’habitude : Memory<T> est générique. On peut créer Memory<int>, Memory<byte>, Memory<char> ou même Memory<MyType>. À l’intérieur, Memory<T> contient une référence vers un tableau, une string ou une autre source de données, et aussi une indication de la plage (à partir de quel index et combien d’éléments).

Pour obtenir depuis Memory<T> un accès rapide pour traitement, on utilise sa propriété Span — ainsi on obtient immédiatement un Span<T> qu’on peut utiliser à l’intérieur d’une méthode synchrone.

4. Comment créer Memory<T> : pratique

Exemple 1. Création à partir d’un tableau

int[] numbers = { 1, 2, 3, 4, 5, 6 };
Memory<int> memory = new Memory<int>(numbers); // Tout le tableau

// On peut prendre un "slice" — une partie du tableau
Memory<int> slice = memory.Slice(2, 3); // éléments 2, 3 et 4

Exemple 2. Création à partir d’une string (via Memory<char>)

string text = "Privet, mir!";
Memory<char> charMemory = text.AsMemory(); // Tout le texte comme mémoire
Memory<char> subMemory = charMemory.Slice(7, 3); // depuis le 7ème caractère, 3 caractères ("mir")

Exemple 3. Utilisation de ReadOnlyMemory<T>

Exactement pareil, mais protégé contre les modifications :

int[] data = { 10, 20, 30, 40 };
ReadOnlyMemory<int> readOnly = data; // Ne permettra pas de modifier via cet objet

5. Conversion entre Memory<T> et Span<T>

Travailler avec Memory<T> aussi "souplement" et rapidement que avec Span<T> n’est pas possible directement — c’est prévu pour autre chose. Mais quand il faut vraiment traiter rapidement un morceau de mémoire, on peut obtenir un Span<T> "instantané" depuis la mémoire via la propriété Span :

void ProcessData(Memory<int> memory)
{
    Span<int> span = memory.Span;
    for (int i = 0; i < span.Length; i++)
    {
        span[i] += 100;
    }
}

Notez : Span<T> ne fonctionne que dans la méthode. Si vous essayez de le retourner à l’extérieur, le compilateur renverra une erreur.

Avec ReadOnlyMemory<T> c’est identique, sauf que vous obtiendrez un ReadOnlySpan<T> qui n’autorise pas la modification :

void PrintData(ReadOnlyMemory<int> memory)
{
    ReadOnlySpan<int> roSpan = memory.Span;
    foreach (var item in roSpan)
        Console.WriteLine(item);
}

6. Utilisation dans des cas réels

Traitement asynchrone de données

C’est là que Memory<T> prend tout son sens. Il peut être utilisé dans des méthodes asynchrones ! Par exemple, lecture asynchrone de fichiers :

using System.IO;
using System.Threading.Tasks;

public async Task ReadFileAsync(string path)
{
    byte[] buffer = new byte[4096];
    using var stream = File.OpenRead(path);
    int bytesRead = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length));
    // Maintenant vous pouvez travailler avec buffer
}

Ici AsMemory passe le buffer directement dans la méthode asynchrone, sans souci de portée ou de destruction de la mémoire, contrairement à ce qui se passerait avec Span<T>.

Stocker des slices dans des propriétés et champs

Parfois il faut une classe qui garde un "morceau" d’un grand tableau pour l’envoyer plus tard :

class DataChunk
{
    public Memory<byte> Data { get; }

    public DataChunk(Memory<byte> data)
    {
        Data = data;
    }
}

7. Utilisation avec collections, strings et tableaux

Avec les tableaux

Le plus commun :

byte[] bytes = { 1, 2, 3, 4, 5 };
Memory<byte> mem = bytes;          // Tout le tableau
Memory<byte> part = mem.Slice(2);  // Depuis le 3ème élément jusqu'à la fin

Avec les strings

Via AsMemory() :

string hello = "Hello, Memory!";
ReadOnlyMemory<char> mem = hello.AsMemory(6, 6); // "Memory"

Avec les collections (par ex. List<T>)

On ne peut pas directement créer un Memory<T> depuis un List<T>. Il faut passer par un tableau :

List<int> list = new List<int> { 1, 2, 3 };
Memory<int> mem = list.ToArray(); // Copie, pas une référence !

Faites attention : si vous voulez éviter la copie — gardez vos données dans un tableau.

8. Erreurs typiques en travaillant avec Memory<T>

Erreur №1 : tenter d’utiliser Span<T> à la place de Memory<T> dans les champs de classe. On ne peut pas stocker un Span<T> dans un champ de classe car il est lié à la pile. Le compilateur lèvera une erreur. Utilisez Memory<T> pour le stockage sur le tas.

Erreur №2 : s’attendre à une copie lors du slicing. Memory<T> ne copie pas les données, il crée une "fenêtre" sur le tableau existant. Si vous modifiez les données via un Memory<T>, cela se reflétera dans tous les autres qui pointent vers la même mémoire.

Erreur №3 : tenter de créer Memory<T> directement depuis List<T>. Memory<T> fonctionne seulement avec des tableaux, parce que List<T> peut changer l’emplacement des données en mémoire. Transformez la liste en tableau via ToArray().

Erreur №4 : ignorer ReadOnlyMemory<T> pour des données immuables. Si les données ne doivent pas être modifiées, utilisez ReadOnlyMemory<T> au lieu de Memory<T> pour plus de sécurité.

1
Étude/Quiz
Mémoire en C#, niveau 65, leçon 4
Indisponible
Mémoire en C#
Organisation de la mémoire dans .NET
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION