C# — Cheat Sheet

Tag: csharp, dotnet, clr, linq, async, oop

Livello: intermedio

TL;DR: Linguaggio fortemente tipizzato e orientato agli oggetti per runtime .NET. Gestione memoria tramite CLR e Garbage Collector con supporto nativo per asincronia e LINQ.

Concetti chiave

Compilazione in Common Intermediate Language (CIL/IL) eseguito dalla Common Language Runtime (CLR).

Value types memorizzati su stack (struct, primitivi, enum); reference types allocati sull'heap (class, interface, delegate).

Garbage Collector generazionale (Gen 0, Gen 1, Gen 2 e Large Object Heap) per il recupero automatico della memoria.

Asincronia cooperativa basata su Task e state machine generata dal compilatore tramite async/await.

LINQ (Language Integrated Query) unifica l'interrogazione di collezioni in-memory e sorgenti esterne (IQueryable vs IEnumerable).

Pattern matching avanzato (type pattern, relational pattern, property pattern) per ridurre type casting esplicito.

Record types immutabili per definizione con supporto a with expressions e value equality automatica.

Nullable Reference Types per rilevare potenziali NullReferenceException a tempo di compilazione.

Domande tipiche e risposte pronte

Q: Qual è la differenza sostanziale tra struct e class in C#?R: La struct è un value type memorizzato nello stack o inline, copiato per valore. La class è un reference type allocato sull'heap, gestito dal Garbage Collector e passato per riferimento.

Q: Come funziona il costrutto using e quale interfaccia richiede?R: Richiede l'interfaccia IDisposable. Garantisce la chiamata automatica al metodo Dispose() per liberare risorse non gestite, traducendosi internamente in un blocco try/finally.

Q: Qual è la differenza tra IEnumerable<T> e IQueryable<T>?R: IEnumerable<T> esegue i filtri in memoria nell'applicazione client. IQueryable<T> traduce l'espressione (Expression Tree) nella query nativa della sorgente, ad esempio SQL con Entity Framework.

Q: Perché evitare async void tranne che negli event handler?R: async void non restituisce un Task tracciabile. Le eccezioni non gestite non possono essere catturate dal chiamante e causano il crash del processo.

Insidie e errori comuni

Boxing e unboxing non intenzionali convertendo value types a object o interfacce, con overhead sull'heap.

Chiamare .Result o .Wait() su Task asincroni generando deadlock sul SynchronizationContext.

Mancata cancellazione della sottoscrizione a eventi con delegate, che causa memory leak impedendo la deallocazione via GC.

Modifica concorrente di collezioni standard non thread-safe invece di utilizzare System.Collections.Concurrent.

Closure capture errata di variabili dentro lambda o cicli for/foreach.

Snippet e comandiC#

// Esempio record e pattern matching
public record User(int Id, string Role, bool IsActive);

public decimal GetDiscount(User user) => user switch
{
    { IsActive: false } => 0m,
    { Role: "Admin" } => 0.30m,
    { Role: "Customer" } => 0.10m,
    _ => 0.05m
};

C#

// Gestione asincrona corretta con CancellationToken
public async Task<string> FetchDataAsync(HttpClient client, string url, CancellationToken ct)
{
    using var response = await client.GetAsync(url, ct);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync(ct);
}

Da ripassare

[ ] Meccanismo di allocazione LOH (Large Object Heap) e compattazione

[ ] Differenze tra ValueTask<T> e Task<T> per ottimizzazione allocazioni

[ ] Funzionamento interno degli Expression Trees in LINQ

[x] Utilizzo di IDisposable e pattern Dispose standard

[x] Sintassi e semantica di async/await

Jarvis Interviews
Jarvis