CodeGym /Corsi /C# SELF /Nuove possibilità Span<...

Nuove possibilità Span<T>

C# SELF
Livello 65 , Lezione 3
Disponibile

1. Introduzione

Immagina di avere un grande array di dati, per esempio bytes letti da un file. Vuoi elaborare un frammento — leggere una parte come stringa o semplicemente passare un segmento di dati a un metodo. Nello "stile vecchio" avresti copiato quei bytes in un nuovo array — è lento e consuma memoria. E se quei frammenti sono decine o centinaia?

Ed ecco il protagonista — Span<T>. È una struttura speciale che descrive una vista (view) su un array (o su qualsiasi blocco contiguo di memoria), senza copiare i dati, indicando semplicemente l'area desiderata. Importante: Span<T> non è una nuova collection, ma una "finestra" sicura su un array esistente!

Caratteristiche principali e limiti di Span<T>

  • Span<T> è una struct (value type) che rappresenta una "finestra" sulla memoria e non copia gli elementi.
  • Si riferisce solo a un blocco contiguo di memoria: array, parte di array, blocco allocato con stackalloc, memoria della stringa tramite metodi speciali, oppure memoria in codice unsafe.
  • Span<T> vive sullo stack. Non lo puoi conservare in un campo di una classe né restituirlo direttamente da metodi async.
  • Garantisce la sicurezza dei tipi: accesso alla memoria senza lavorare manualmente con i puntatori (se non usi unsafe).

Brevemente sulle nuove feature di C# 14

C# 14 estende il lavoro con i slice: comodi range .. e indici dalla fine ^1, pattern matching migliorati per array e slice e altro "sugar" sintattico. Torneremo su questo alla fine.

2. Come creare Span<T>?

Iniziamo da un array semplice e creiamo uno span:

int[] numbers = { 10, 20, 30, 40, 50, 60 };
Span<int> midSpan = new Span<int>(numbers, 2, 3); // prendere 3 elementi a partire dal 3° (indice 2)

Ora midSpan punta agli elementi 30, 40, 50 — non è una copia, ma una vista sugli elementi "vivi" dell'array. Modificandoli tramite lo span, modifichi l'array originale.

midSpan[0] = 100;
Console.WriteLine(numbers[2]); // Stamperà 100

Perché non usare slice di array che copiano?

Il classico slice con LINQ crea un nuovo array:

int[] slice = numbers.Skip(2).Take(3).ToArray(); // <-- qui si crea una copia!

Questo è costoso in tempo e memoria. Span risolve il problema delle copie inutili — e funziona non solo con numeri.

3. Altri modi per creare Span<T>

Array esistente

Span<int> mySpan = numbers; // Conversione implicita da array a Span

Parte di un array

int[] numbers = {10, 20, 30, 40, 50, 60};
Span<int> part = numbers.AsSpan(1, 4); // 4 elementi a partire dall'indice 1: {20, 30, 40, 50}

Memoria dello stack (stackalloc)

Span<T> permette di allocare array sullo stack:

Span<byte> buffer = stackalloc byte[128];
for (int i = 0; i < buffer.Length; i++)
    buffer[i] = (byte)i;

La memoria dello stack è veloce e viene liberata automaticamente all'uscita dal metodo. Ma la dimensione deve essere ragionevole — megabyte nello stack non sono accettabili.

Stringhe e ReadOnlySpan<char>

Le stringhe in .NET sono immutabili, quindi usiamo ReadOnlySpan<char>:

string greeting = "Hello, C# world!";
ReadOnlySpan<char> span = greeting.AsSpan(7, 8);   // "C# world"
Console.WriteLine(span.ToString());                // C# world

4. Mettiamo insieme un esempio!

using System;

class Program
{
    static void Main()
    {
        int[] orderTotals = { 100, 200, 300, 400, 500, 600, 700 };
        Console.WriteLine("Tutta la cronologia degli ordini: ");
        foreach (int total in orderTotals)
            Console.Write(total + " ");
        Console.WriteLine();
        
        Console.WriteLine("Stampare gli ordini dal 2° al 4° (indici 1-3):");
        Span<int> recentOrders = orderTotals.AsSpan(1, 3);
        foreach (int t in recentOrders)
            Console.Write(t + " ");
        Console.WriteLine();
        
        // Mutiamo i dati tramite Span
        recentOrders[1] = 999;
        Console.WriteLine("Dopo la modifica tramite Span:");
        foreach (int total in orderTotals)
            Console.Write(total + " ");
        Console.WriteLine();
    }
}

Il slice recentOrders lavora realmente sull'array — non è una copia.

5. Sicurezza, performance e controlli

  • Uso parsimonioso della memoria: niente copie inutili.
  • Controlli dei limiti proteggono dall'out-of-bounds.
  • Le ottimizzazioni JIT forniscono accesso molto veloce (senza unsafe).

Interazione con i metodi

I metodi possono accettare Span<T> o ReadOnlySpan<T>. Se non prevedi di modificare i dati — usa ReadOnlySpan<T>.

static int Sum(Span<int> slice)
{
    int sum = 0;
    foreach (var item in slice)
        sum += item;
    return sum;
}

int[] data = { 1, 2, 3, 4, 5, 6, 7 };
Console.WriteLine(Sum(data.AsSpan(2, 3))); // 3+4+5=12

6. Uso dei range

A partire da C# 8 sono disponibili i range .. e gli indici dalla fine ^ — funzionano molto bene con i slice.

int[] arr = { 10, 20, 30, 40, 50, 60 };

Span<int> span = arr[2..5]; // indici 2,3,4 - cioè 30, 40, 50

Nel range l'indice iniziale è incluso, quello finale è escluso.

Indici dalla fine

int lastElement = arr[^1];     // ultimo elemento (60)
Span<int> lastTwo = arr[^2..]; // gli ultimi due elementi (50, 60)

Range e stringhe

string code = "SpanMagic!";
var mid = code[4..9]; // "Magic"

Questo slice è un ReadOnlySpan<char>; per ottenere una stringa, chiama ToString().

7. Pattern moderni per lavorare con slice (C# 14)

I nuovi pattern rendono più semplice analizzare array e slice.

if (arr is [10, 20, .. var rest]) // .. cattura il "resto" dell'array
{
    Console.WriteLine("L'inizio coincide, il resto:");
    foreach (var x in rest)
        Console.WriteLine(x);
}
if (arr is [.., 50, 60])
    Console.WriteLine("L'array termina con 50, 60");

Confronto: Array, ArraySegment e Span

Tipo Mutabile Copia i dati? Si può allocare sullo stack Class/struct Si può tenere come campo di classe
T[]
- no class
ArraySegment<T>
no no struct
Span<T>
no struct no
ReadOnlySpan<T>
no no struct no

In altre parole, Span<T> è l'evoluzione di ArraySegment<T>: più performante e sicuro.

8. Errori tipici e insidie

Tentare di conservare Span<T> in un campo di una classe. Non è permesso: i campi vivono sull'heap, mentre Span<T> deve vivere sullo stack. Usa ArraySegment<T> o indici per indirizzare i dati.

Restituire/tenere Span<T> da metodi async. Non è consentito: l'async rompe lo stack. Passa i dati in altro modo — ad esempio come array, Memory<T>/ReadOnlyMemory<T>.

Range non valido quando si crea il slice. L'uscita dai limiti genera un'eccezione a runtime. Controlla sempre length e confini prima di creare il slice.

Dimenticare che lo span riflette i dati originali. Qualsiasi modifica tramite Span<T> cambia l'array originale. Se ti serve una copia indipendente — copia i dati esplicitamente.

2
Compito
C# SELF, livello 65, lezione 3
Bloccato
Utilizzo di range e indice inverso per mostrare una parte dell'array
Utilizzo di range e indice inverso per mostrare una parte dell'array
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION