Operatori di null in C#

Suggerimento

Questo articolo fa parte della sezione Nozioni fondamentali per gli sviluppatori che conoscono almeno un linguaggio di programmazione e stanno imparando C#. Se non si ha familiarità con la programmazione, iniziare prima con le esercitazioni introduttive . Per informazioni di riferimento sull'operatore completo, vedere Operatori di accesso ai membri e operatori null-coalescing nel riferimento al linguaggio.

C# offre diversi operatori che rendono conciso il codice null-safe. Anziché annidare if (x != null) le guardie in tutto il codice, questi operatori consentono di esprimere in una singola espressione l'accesso sicuro ai valori nulli, i valori di fallback e i test di null.

Questo articolo illustra ?. e ?[] per l'accesso condizionale null, ?? per l'operatore di coalescenza null, ??= per l'assegnazione con coalescenza null e is null/is not null per il confronto di pattern null.

Accesso ai membri condizionali Null ?.

L'operatore ?. accede a un membro solo quando l'oggetto è diverso da Null. Quando l'oggetto è null, l'intera espressione restituisce null invece di generare un NullReferenceExceptionoggetto :

string? name = null;

// Without ?., accessing a member on null throws NullReferenceException:
// int len = name.Length; // throws if name is null

// ?. returns null instead of throwing:
int? len = name?.Length;
Console.WriteLine(len.HasValue); // False

name = "C#";
Console.WriteLine(name?.Length); // 2

L'operatore ?.: quando l'operando sinistro è , tutto ciò che è a destra viene ignorato. Nessuna chiamata al metodo viene eseguita e non si verificano effetti collaterali.

È possibile concatenare più ?. operatori in una singola espressione. La catena si ferma al primo null incontrato.

string? input = null;

// Chain ?. across multiple method calls — short-circuits at the first null:
string? upper = input?.Trim()?.ToUpperInvariant();
Console.WriteLine(upper ?? "(none)"); // (none)

input = "  hello  ";
Console.WriteLine(input?.Trim()?.ToUpperInvariant()); // HELLO

Accesso dell'indicizzatore condizionale Null ?[]

L'operatore ?[] applica lo stesso comportamento a corto circuito all'indicizzatore e all'accesso alla matrice. Usarlo quando la raccolta stessa potrebbe essere null:

string[]? tags = null;

// ?[] accesses an element only when the collection is non-null
string? first = tags?[0];
Console.WriteLine(first ?? "(none)"); // (none)

tags = ["csharp", "dotnet", "nullable"];
Console.WriteLine(tags?[0]);          // csharp

Concatenare operatori null-condizionali

Collegare più ?. operatori per attraversare un percorso di riferimenti potenzialmente null. Cortocircuito della catena al primo null:

var order = new Order("ORD-001", null);

// Each ?. short-circuits when null: Customer is null, so Address and City are never accessed
string? city = order.Customer?.Address?.City;
Console.WriteLine(city ?? "(no city)"); // (no city)

var fullOrder = new Order("ORD-002",
    new Customer("Alice", new Address("123 Main St", "Springfield", "IL")));

Console.WriteLine(fullOrder.Customer?.Address?.City); // Springfield

Quando Customer è null, né AddressCity viene valutato. L'intera espressione restituisce null.

Invocazione di delegati thread-safe

?. fornisce un modo pulito e thread-safe per richiamare un delegato o generare un evento. L'espressione del delegato viene valutata una sola volta, quindi non esiste alcuna finestra per un altro thread per annullare la sottoscrizione tra il controllo Null e la chiamata:

EventHandler? clicked = null;

// No subscribers — ?.Invoke does nothing instead of throwing NullReferenceException
clicked?.Invoke(null, EventArgs.Empty);

clicked += (_, _) => Console.WriteLine("Button clicked!");

// With a subscriber — ?.Invoke calls the handler
clicked?.Invoke(null, EventArgs.Empty);
// Output: Button clicked!

Questo modello sostituisce il linguaggio precedente if (clicked != null) clicked(...) .

Operatore di coalescenza dei null ??

L'operatore ?? restituisce l'operando a sinistra quando è diverso da Null e l'operando di destra quando la sinistra è null. Usarlo per specificare un valore predefinito:

string? username = null;

// ?? returns the right-hand value when the left-hand is null
string display = username ?? "Guest";
Console.WriteLine(display); // Guest

username = "alice";
display  = username ?? "Guest";
Console.WriteLine(display); // alice

?? è associativo a destra, quindi a ?? b ?? c si valuta come a ?? (b ?? c). Il primo valore non Null prevale. Un modello comune consiste nel concatenare ?. con ??: usare ?. per attraversare in modo sicuro una catena null-possibile, quindi ?? sostituire un valore predefinito se la catena ha restituito null. Per un esempio completo, vedere Combinare operatori Null.

Assegnazione null-coalescing ??=

L'operatore ??= assegna il valore di destra a una variabile solo quando la variabile è null. Utilizzalo per l'inizializzazione pigra:

List<string>? cache = null;

// ??= assigns only when the variable is null
cache ??= LoadData();
Console.WriteLine(cache.Count); // 3

// cache is already non-null, so LoadData() isn't called again
cache ??= LoadData();
Console.WriteLine(cache.Count); // 3

static List<string> LoadData() => ["alpha", "beta", "gamma"];

L'espressione a destra viene valutata solo quando la variabile è null. Quando la variabile ha già un valore, il lato destro non viene valutato affatto.

Assegnazione null-conditionale (C# 14)

A partire da C# 14, è possibile usare ?. e ?[] come destinazioni di assegnazione. L'assegnazione viene eseguita solo quando l'oggetto a sinistra non è nullo.

AppConfig? config = new AppConfig();

// Assigns only when config is non-null (C# 14)
config?.Theme = "dark";
Console.WriteLine(config?.Theme); // dark

AppConfig? missing = null;
missing?.Theme = "light";                         // no-op: missing is null
Console.WriteLine(missing?.Theme ?? "(no config)"); // (no config)

Il lato destro viene valutato solo quando il lato sinistro è noto per essere non nullo.

Corrispondenza di modelli Null: is null e is not null

I is null modelli e is not null testano se un'espressione è null:

string? input = null;

// is null is the preferred test — unaffected by operator overloading
if (input is null)
{
    Console.WriteLine("No input provided.");
}

// == null also works, but a custom == operator can change its behavior
if (input == null)
{
    Console.WriteLine("Still no input.");
}

Preferire is null rispetto a == null per i controlli null. L'operatore == può essere sottoposto a overload, ovvero x == null può restituire true anche quando x non null è se il tipo definisce un operatore di uguaglianza personalizzato. Il is null modello verifica sempre il riferimento null effettivo, indipendentemente dagli operatori sovraccaricati.

string? value = "hello";

if (value is not null)
{
    Console.WriteLine(value.ToUpper()); // HELLO
}

Combinare operatori Null

In pratica, spesso si combinano diversi di questi operatori. Un'espressione può attraversare in modo sicuro un grafico di oggetti profondi, applicare un fallback e quindi proteggere il risultato:

Order? order = GetPendingOrder();

// Chain ?. for safe traversal, ?? for a fallback, is null for a clear guard
string city = order?.Customer?.Address?.City ?? "unknown";

if (order is null)
{
    Console.WriteLine("No pending order.");
}
else
{
    Console.WriteLine($"Shipping to: {city}");
}
// Output: No pending order.

Operatore Null-forgiving !

L'operatore ! postfix sopprime gli avvisi nullable. Aggiungere ! per indicare al compilatore "questa espressione non è sicuramente null". L'operatore non ha alcun effetto in fase di esecuzione. Influisce solo sull'analisi dello stato Null del compilatore.

string? name = FindUser("alice");

// Use ! only when you have information the compiler doesn't.
// FindUser guarantees a non-null result for known usernames.
int length = name!.Length;
Console.WriteLine(length); // 5

Usa ! con moderazione e solo quando si dispone di informazioni che il compilatore non ha. Gli esempi includono test che passano intenzionalmente null per convalidare la logica di controllo degli argomenti o chiamare un metodo il cui contratto garantisce un ritorno non nullo per un input noto. L'utilizzo eccessivo di ! sconfigge lo scopo dei tipi di riferimento nullable. Per una spiegazione completa, vedere Tipi di riferimento Nullable.

Vedi anche