Operatory null języka C#

Tip

Ten artykuł jest częścią sekcji Podstawy dla deweloperów, którzy znają co najmniej jeden język programowania i uczą się języka C#. Jeśli dopiero zaczynasz programować, najpierw zacznij od samouczków Wprowadzenie . Aby uzyskać pełną dokumentację operatorów, zobacz Operatory dostępu do składowych i operatory łączenia wartości null w dokumentacji języka.

Język C# udostępnia kilka operatorów, które umożliwiają zwięzłe pisanie kodu bezpiecznego względem wartości null. Zamiast zagnieżdżać if (x != null) strażników w całym kodzie, te operatory umożliwiają wyrażanie bezpiecznego dostępu o wartościach null, wartości rezerwowych i testów null w jednym wyrażeniu.

W tym artykule opisano ?. i ?[] w przypadku dostępu warunkowego na wartość null, ?? dla łączenia wartości null, ??= przypisania łączenia wartości null oraz is null/is not null dla dopasowania wzorca null.

Dostęp warunkowy o wartości null ?.

Operator ?. uzyskuje dostęp do członka tylko wtedy, gdy obiekt nie ma wartości null. Gdy obiekt jest null, całe wyrażenie przyjmuje wartość null zamiast generować NullReferenceException.

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

?. operatora: gdy lewa strona to null, wszystko po prawej stronie jest pomijane. Żadne wywołania metod nie są wykonywane, a skutki uboczne nie występują.

W jednym wyrażeniu można połączyć wiele ?. operatorów. Łańcuch zatrzymuje się na pierwszym napotkanym null.

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

Dostęp indeksatora warunkowego o wartości null ?[]

Operator ?[] stosuje to samo zachowanie zwarciowe dla indeksatora i dostępu do tablicy. Użyj tego, gdy sama kolekcja może mieć wartość 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

Łączenie operatorów warunkowych z wartością null

Połącz wiele ?. operatorów, aby przejść przez ścieżkę potencjalnie pustych odwołań. Łańcuch zwarcia występuje przy pierwszym 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

Gdy Customer jest null, ani Address ani City nie jest obliczane. Całe wyrażenie zwraca wartość null.

Wywołanie bezpiecznego dla wątków delegata

?. Zapewnia czysty, bezpieczny wątkowo sposób wywoływania delegata lub zgłaszania zdarzenia. Wyrażenie delegata jest oceniane tylko raz, więc nie ma okna dla innego wątku, aby anulować subskrypcję między sprawdzaniem wartości null a wywołaniem:

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!

Ten wzorzec zastępuje starszy if (clicked != null) clicked(...) idiom.

Łączenie wartości null ??

Operator ?? zwraca operand po lewej stronie, gdy nie jest wartością null, a operand po prawej stronie, gdy lewy jest null. Użyj go, aby podać wartość domyślną:

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

?? jest prawostronnie wiążący, dlatego a ?? b ?? c wynika jako a ?? (b ?? c). Pierwsza wartość, która nie jest null, wygrywa. Typowy wzorzec polega na połączeniu ?. z ??: użyj ?. metody, by bezpiecznie przechodzić przez łańcuch, który może mieć wartość null, a następnie ?? do zastąpienia wartością domyślną, jeśli łańcuch zwrócił null. Pełny przykład można znaleźć w temacie Łączenie operatorów o wartości null.

Przypisanie z użyciem operatora łączenia null ??=

Operator ??= przypisuje wartość prawą do zmiennej tylko wtedy, gdy zmienna to null. Użyj go do leniwego inicjalizowania:

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"];

Wyrażenie po prawej stronie jest obliczane tylko wtedy, gdy zmienna to null. Gdy zmienna ma już wartość, prawa strona nie jest w ogóle oceniana.

Przypisanie warunkowe o wartości null (C# 14)

Począwszy od C# 14, można używać ?. i ?[] jako obiekty przypisania. Przypisanie jest uruchamiane tylko wtedy, gdy obiekt po lewej stronie ma wartość inną niż null:

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)

Prawa strona jest ewaluowana tylko wtedy, gdy lewa strona nie jest pusta.

Dopasowywanie wzorca o wartości null: is null i is not null

Wzorce is null i is not null testują, czy wyrażenie ma wartość 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.");
}

Preferuj is null zamiast == null do sprawdzania wartości null. Operator == może być przeciążony, co oznacza, że x == null może zwrócić true nawet wtedy, gdy x nie jest null, jeśli typ definiuje niestandardowy operator równości. Wzorzec is null zawsze sprawdza rzeczywiste odwołanie o wartości null, niezależnie od przeciążenia operatora.

string? value = "hello";

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

Łączenie operatorów o wartości null

W praktyce często łączy się kilka z tych operatorów. Jedno wyrażenie może bezpiecznie przechodzić przez złożoną strukturę obiektów, zastosować mechanizm rezerwowy, a następnie sprawdzać wynik:

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.

Operator pomijający null !

Operator ! postfiksowy pomija ostrzeżenia o wartości null. Dołącz !, aby poinformować kompilator, że "to wyrażenie na pewno nie ma wartości null." Operator nie ma wpływu na czas wykonywania. Ma to wpływ tylko na analizę stanu null kompilatora.

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

Używaj ! oszczędnie i tylko wtedy, gdy masz informacje, których kompilator nie ma. Przykłady obejmują testy, które celowo przekazują null w celu weryfikacji logiki sprawdzania argumentów, lub wywołanie metody, której kontrakt gwarantuje zwrócenie wartości różnej od null dla znanych danych wejściowych. Nadmierne użycie ! niweczy cel typów odwołań dopuszczających wartość null. Aby uzyskać pełne wyjaśnienie, zobacz Typy referencyjne dopuszczane do wartości null.

Zobacz także