CodeGym /Corsi /C# SELF /Memory<T> e <...

Memory<T> e ReadOnlyMemory<T>

C# SELF
Livello 65 , Lezione 4
Disponibile

1. Introduzione

Tutto è iniziato dal desiderio degli sviluppatori .NET di velocizzare il lavoro con grandi quantità di dati e dare agli sviluppatori la possibilità di farlo nel modo più comodo e sicuro possibile. Prima è comparso Span<T>, che è una "finestra" su un'area di memoria contigua, capace di mostrare parte di un array, di una stringa o anche memoria allocata fuori da .NET (per esempio tramite P/Invoke).

Tuttavia Span<T> ha un vincolo potente: deve sempre vivere solo sullo stack. Non può essere conservato in campi di classi, non può essere restituito da metodi, non può essere passato tra metodi asincroni. La ragione è la sicurezza: se qualcuno tenesse una referenza a una memoria che non esiste più, l'applicazione andrebbe sicuramente in crash.

A volte è necessario restituire slice di dati dai metodi, conservarli in collezioni o campi delle classi, e anche usarli nelle API asincrone. Qui entra in scena Memory<T> — di fatto è la versione sicura "a lunga durata" di Span<T>, che può vivere sull'heap, essere passata tra thread, "vivere" in proprietà e oggetti e comportarsi come un normale oggetto .NET.

C'è anche un "fratello maggiore" — ReadOnlyMemory<T>, che, come puoi immaginare, non permette di modificare i dati sottostanti, ma consente di leggerli ovunque serva.

2. Qual è la differenza tra Span<T> e Memory<T>

Ecco una piccola tabella per confronto visivo:

Span<T>
Memory<T>
Dove vive Solo sullo stack (stack only) Sullo stack e sull'heap (heap/stack)
Può essere conservato in un campo ❌ No ✅ Sì
Può essere restituito da un metodo ❌ No ✅ Sì
Metodi asincroni/await ❌ No ✅ Sì
Modificabile ✅ Esiste anche ReadOnlySpan<T> ✅ Esiste anche ReadOnlyMemory<T>
Permette di fare slice dei dati ✅ Sì ✅ Sì

Se devi "scorrere" rapidamente i dati dentro un metodo — usa Span<T>. Se devi restituire il risultato all'esterno o metterlo in un campo di una classe — usa Memory<T>. E se i dati sono solo in lettura — usa ReadOnlyMemory<T>.

3. Firma e struttura base di Memory<T>

Come sempre: Memory<T> è generic, cioè un tipo generico. Puoi creare Memory<int>, Memory<byte>, Memory<char> e persino Memory<MyType>. Dentro Memory<T> c'è una referenza a un array, a una stringa o a un'altra sorgente di dati, e anche l'indicazione del range (da quale indice e quanti elementi).

Per ottenere accesso veloce a Memory<T> per l'elaborazione, si usa la sua proprietà Span — così ottieni immediatamente un Span<T> che può essere usato all'interno di un metodo sincrono.

4. Come creare Memory<T>: pratica

Esempio 1. Creazione da un array

int[] numbers = { 1, 2, 3, 4, 5, 6 };
Memory<int> memory = new Memory<int>(numbers); // L'intero array

// Puoi prendere uno "slice" — parte dell'array
Memory<int> slice = memory.Slice(2, 3); // elementi 2, 3 e 4

Esempio 2. Creazione da una stringa (tramite Memory<char>)

string text = "Privet, mir!";
Memory<char> charMemory = text.AsMemory(); // L'intero testo come memoria
Memory<char> subMemory = charMemory.Slice(7, 3); // dal carattere 7, 3 caratteri ("mir")

Esempio 3. Uso di ReadOnlyMemory<T>

Esattamente lo stesso, solo protetto dalle modifiche:

int[] data = { 10, 20, 30, 40 };
ReadOnlyMemory<int> readOnly = data; // Non permetterà modifiche tramite questo oggetto

5. Conversione tra Memory<T> e Span<T>

Non si può lavorare con Memory<T> con la stessa flessibilità e velocità di Span<T> direttamente — è pensato per scopi diversi. Ma quando hai davvero bisogno di processare velocemente un pezzo di memoria, puoi ottenere uno Span<T> "istantaneo" dalla memoria tramite la proprietà Span:

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

Nota: Span<T> funziona solo all'interno del metodo. Se provi a restituirlo all'esterno, il compilatore segnalerà un errore.

Con ReadOnlyMemory<T> è analogo, solo che ottieni un ReadOnlySpan<T> che non ti permette di modificare i dati:

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

6. Uso in scenari reali

Elaborazione asincrona dei dati

Qui Memory<T> mostra il suo vero valore. Può essere usato nei metodi asincroni! Per esempio, lettura asincrona da file:

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));
    // Ora puoi lavorare con buffer
}

Qui AsMemory passa il buffer direttamente al metodo asincrono, senza problemi di scope o di distruzione della memoria, come sarebbe il caso con Span<T>.

Conservare slice di dati in proprietà e campi

A volte serve una classe che conserva un "pezzo" di un grande array per uso successivo:

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

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

7. Uso con collezioni, stringhe e array

Con array

Molto spesso:

byte[] bytes = { 1, 2, 3, 4, 5 };
Memory<byte> mem = bytes;          // L'intero array
Memory<byte> part = mem.Slice(2);  // Dal terzo elemento fino alla fine

Con stringhe

Tramite AsMemory():

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

Con collezioni (per esempio List<T>)

Non puoi creare direttamente Memory<T> da una List<T>. Solo passando per un array:

List<int> list = new List<int> { 1, 2, 3 };
Memory<int> mem = list.ToArray(); // Copia, non riferimento!

Attenzione: se vuoi evitare copie — tieni i dati in un array.

8. Errori tipici quando si lavora con Memory<T>

Errore №1: tentare di usare Span<T> invece di Memory<T> nei campi di una classe. Non si può conservare Span<T> nei campi di una classe, perché è legato allo stack. Il compilatore segnalerà un errore. Usa Memory<T> per conservare nell'heap.

Errore №2: aspettarsi una copia dei dati quando fai slice. Memory<T> non copia i dati, crea una "finestra" sull'array esistente. Se modifichi i dati tramite un Memory<T>, questo si riflette su tutti gli altri riferimenti alla stessa memoria.

Errore №3: tentare di creare Memory<T> direttamente da List<T>. Memory<T> funziona solo con array, perché List<T> può cambiare la posizione dei suoi dati in memoria. Trasforma la list in array con ToArray().

Errore №4: ignorare ReadOnlyMemory<T> per dati immutabili. Se i dati non devono essere modificati, usa ReadOnlyMemory<T> invece di Memory<T> per maggiore sicurezza.

1
Sondaggio/quiz
Memoria in C#, livello 65, lezione 4
Non disponibile
Memoria in C#
Come funziona la memoria in .NET
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION