Il linguaggio C# è noto per la sua sicurezza e l’astrazione dai dettagli di basso livello, ma esistono scenari dove serve sporcarsi le mani con la memoria.
In questi casi entrano in gioco le parole chiave unsafe e fixed, strumenti potenti ma spesso fraintesi, che permettono di lavorare con puntatori e memoria nativa — senza rinunciare alla stabilità e alla prevedibilità del runtime .NET.
In questo articolo vedremo quando ha senso usarli, come farlo in modo corretto, e quali sono le best practice per evitare bug e memory leak.
Cosa significa “unsafe” in C#
Il modificatore unsafe indica al compilatore che il codice al suo interno non è soggetto ai controlli di sicurezza tipici del CLR.
Significa che puoi:
- accedere direttamente a indirizzi di memoria;
- usare puntatori (
int*,byte*, ecc.); - manipolare buffer e strutture native;
- interagire con codice non gestito (C/C++ via interop).
Esempio semplice:
unsafe
{
int value = 42;
int* ptr = &value;
Console.WriteLine(*ptr); // Output: 42
}
Per compilare codice “unsafe”, devi abilitare l’opzione nel progetto (ad esempio in .csproj):
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
Cosa fa il blocco fixed
In C#, gli oggetti gestiti possono essere spostati dal Garbage Collector (GC) in memoria.
Questo è un problema se vogliamo ottenere un puntatore fisso a una variabile gestita.
Il blocco fixed serve proprio a questo: “pinna” l’oggetto in memoria impedendo al GC di muoverlo.
Esempio:
unsafe
{
int[] data = { 10, 20, 30 };
fixed (int* ptr = data)
{
for (int i = 0; i < data.Length; i++)
Console.WriteLine(*(ptr + i));
}
}
In questo modo ptr punta in modo stabile al primo elemento dell’array, anche se il GC effettua una collection.
Uscendo dal blocco fixed, l’oggetto viene “unpinnato”.
Quando ha senso usare unsafe e fixed
Molti sviluppatori evitano l’uso di unsafe, ma in realtà è perfettamente legittimo — purché sia motivato e confinato.
Ecco i casi tipici dove ha senso usarlo:
1 Performance critica su grandi dataset
Se stai processando milioni di elementi (es. immagini, audio, segnali), l’accesso diretto ai buffer può ridurre l’overhead del runtime.
2 Interoperabilità con librerie native
Quando integri C# con librerie C/C++ (via P/Invoke), potresti dover manipolare puntatori o passare buffer nativi.
3 Serializzazione o parsing binario
Lavorare con strutture a layout fisso (struct con [StructLayout(LayoutKind.Sequential)]) e usare fixed può evitare conversioni lente.
4 Engine e sistemi embedded
Motori di gioco, moduli grafici o dispositivi IoT spesso richiedono gestione esplicita della memoria per ottenere performance deterministiche.
Best practice per scrivere codice unsafe sicuro
- Isolare il codice unsafe
Crea metodi o moduli separati e mantieni il resto del codice “safe”.internal static unsafe void Copy(byte* src, byte* dest, int count) { ... } - Non fidarti mai dei buffer in ingresso
Valida sempre le dimensioni e limiti l’accesso diretto con condizioni (ife bounds check manuali). - Usa
stackallocquando possibile
Alloca piccoli buffer sullo stack (non sull’heap), evitando il rischio di leak.Span<byte> buffer = stackalloc byte[256]; - Evita pinning prolungato
Bloccare troppi oggetti in memoria riduce l’efficienza del GC. Usafixedsolo per il tempo strettamente necessario. - Preferisci
Span<T>eMemory<T>quando puoi
Dal C# 7.2 in poi, molti casi d’uso diunsafepossono essere sostituiti daSpan<T>che offre performance simili ma con sicurezza.
Alternative moderne a unsafe
Oggi, molte delle operazioni “a basso livello” possono essere realizzate senza codice non sicuro, grazie a:
Span<T>eReadOnlySpan<T>MemoryMarshalArrayPool<T>Memory<T>eIMemoryOwner<T>
Queste API offrono performance comparabili ma con la protezione del runtime.unsafe rimane l’ultima opzione quando serve controllo totale e prevedibilità assoluta.
Conclusione
Scrivere codice unsafe in C# non significa scrivere codice pericoloso.
Significa semplicemente assumersi consapevolmente la responsabilità della memoria, quando il contesto lo richiede — come fanno i sistemi ad alte prestazioni, i game engine o i runtime custom.
Usato bene, unsafe è uno strumento che ti permette di spingere C# oltre i suoi limiti…
senza uscire dal perimetro della sicurezza e della qualità del codice.
1 È possibile usare unsafe in un progetto Blazor o ASP.NET Core?
Sì, ma è raro e sconsigliato. Gli scenari server-side non traggono beneficio dal codice pointer-based, salvo per integrazioni native specifiche.
2 L’uso di unsafe disattiva il Garbage Collector?
No. Il GC continua a funzionare normalmente; l’unica differenza è che può ignorare alcune aree “pinnate” durante la collection.
3 Posso combinare unsafe con Span<T>?
Sì, ma con attenzione: puoi ottenere uno Span<T> da un puntatore usando MemoryMarshal.CreateSpan, utile in interoperabilità.
4 unsafe rallenta la compilazione o l’ottimizzazione JIT?
Al contrario: il JIT può spesso ottimizzare meglio codice pointer-based, specialmente in loop intensivi.
