Перейти к содержимому
Теория · Значимые и ссылочные типы · урок 7 из 11

Операторы ?., ?? и ??=

Вы уже знаете, что переменная класса может хранить null — ссылку, которая никуда не ведёт. Это как письмо, где вместо ссылки на документ осталось пустое место: открыть нечего. Обращение к члену через null останавливает программу исключением NullReferenceException.

Здесь письмо и ссылка расходятся. Пустое место в письме — уже ошибка отправителя, а null в C# — законное значение: его можно хранить, сравнивать и передавать. Останавливает программу только обращение к члену через него.

Поэтому объект, которого может не оказаться, проверяют перед обращением: if (boss != null). Такие проверки нужны постоянно, и для них в C# есть три коротких оператора.

В примерах этого урока у типа стоит знак вопроса: Person?. Он сообщает компилятору, что в переменной может оказаться null, и тот не предупреждает о присваивании null.

Оператор ?.

Оператор ?. обращается к члену, только если слева не null:

C#
Person? boss = null;
string? name = boss?.Name;
Console.WriteLine(name == null); // True
boss = new Person();
boss.Name = "Anna";
Console.WriteLine(boss?.Name); // Anna

class Person
{
    public string Name = "";
}

Давайте разберём пример по строкам:

  1. Person? boss = null; — начальника пока нет.
  2. boss?.Name видит в boss значение null. К полю Name никто не обращается, и всё выражение даёт null. Исключения нет.
  3. Печатается True: в name попал null.
  4. Когда в boss появляется объект, boss?.Name работает как обычное boss.Name и печатает Anna.

Выражение x?.Член читается так: если x равен null, результат — null, иначе — обычное обращение к члену. Через ?. вызывают и методы: name?.ToUpper() при null в name вернёт null вместо исключения.

Оператор ??

Оператор ?? подставляет значение на случай null. Выражение a ?? b даёт a, если a не null, а иначе — b. Вместе с ?. он заменяет целую проверку через if:

C#
Person? guest = null;
Console.WriteLine(guest?.Name ?? "nobody"); // nobody

guest?.Name даёт null, и ?? подставляет "nobody". Если бы в guest был объект, напечаталось бы его имя.

На собеседовании часто спрашивают, как избежать NullReferenceException. Один из ответов — проверять ссылку перед обращением: через if, ?. или ??.

Оператор ??=

Оператор ??= присваивает, только если в переменной сейчас null. Если значение уже есть, переменная не меняется:

C#
string? title = null;
title ??= "Untitled";
title ??= "Draft";
Console.WriteLine(title); // Untitled

Разберём по строкам:

  1. В title лежит null, поэтому первая строка с ??= кладёт в него "Untitled".
  2. Во второй раз в title уже есть строка, и "Draft" не присваивается.
  3. Печатается Untitled.

title ??= "Untitled" — короткая форма для title = title ?? "Untitled". Так задают значение по умолчанию переменной, в которой мог остаться null.

Частая ошибка

Приветствие собрали из строки и ??, но без скобок:

C#
string? user = null;
Console.WriteLine("Hello, " + user ?? "guest"); // ✗

Программа компилируется и печатает Hello, без имени. У ?? приоритет ниже, чем у +, поэтому C# сначала выполняет "Hello, " + user. Сложение строки с null даёт строку "Hello, ": null при сложении превращается в пустую строку. Слева от ?? оказывается не null, и "guest" не подставляется.

Нужны скобки: "Hello, " + (user ?? "guest") печатает Hello, guest.

Главное

  • x?.Член даёт null, если x равен null, и не выбрасывает исключение.
  • a ?? b даёт a, а если там null — b; a ??= b записывает b в a, только если в a лежит null.
  • ?. и ?? часто пишут вместе: guest?.Name ?? "nobody".
  • У ?? приоритет ниже, чем у +, поэтому "Hello, " + (user ?? "guest") пишут со скобками.