Avatar ·

Что означают два вопросительных знака подряд в C#?

Привет! Я только начал изучать C# и наткнулся на странный синтаксис. В чужом коде я увидел вот такую конструкцию: ```csharp string name = userName ?? "Гость"; ``` Что здесь делают эти два вопросительных знака `??`? Я понимаю, что `userName` — это какая-то переменная, но зачем ставить два вопроса и потом строку? Это какая-то проверка? Я пробовал гуглить, но в основном нахожу объяснения про тернарный оператор `? :`, а это выглядит иначе. Ещё я заметил, что иногда пишут `??=` — это тоже связано? Например, в таком коде: ```csharp list ??= new List(); ``` Можете объяснить простыми словами, как это работает и зачем это нужно? Желательно с примерами, потому что я визуал. Заранее спасибо! <<>>

Оператор объединения с null (??) в C#

Два вопросительных знака ?? в C# — это null-coalescing operator (оператор объединения с null). Он используется для того, чтобы проверить значение на null и, если оно null, подставить другое значение. Это очень удобная замена длинным конструкциям с if.

Давайте разберём ваш первый пример:

string name = userName ?? "Гость";

Эта строка работает так:

  1. Проверяется переменная userName.
  2. Если userName НЕ равен null, то в name записывается значение userName.
  3. Если userName равен null, то в name записывается строка "Гость".

Без этого оператора вам пришлось бы писать так:

string name;
if (userName != null)
{
    name = userName;
}
else
{
    name = "Гость";
}

Согласитесь, с ?? гораздо короче и понятнее.

Оператор присваивания объединения с null (??=)

Ваш второй пример с ??= — это более новая фича (появилась в C# 8.0). Это null-coalescing assignment operator. Он делает то же самое, но сразу присваивает значение переменной, если она равна null.

list ??= new List<string>();

Эта строка означает:

  1. Проверяем переменную list.
  2. Если list равен null, то создаём новый список new List<string>() и присваиваем его list.
  3. Если list уже не null, ничего не делаем.

Без ??= вам пришлось бы писать:

if (list == null)
{
    list = new List<string>();
}

Важные моменты

  • Типы должны совпадать или быть совместимыми. Например, если слева int? (nullable int), то справа можно поставить обычный int, но не строку.
  • Оператор можно использовать цепочкой. Например: result = a ?? b ?? c ?? "default"; — будет взято первое не-null значение.
  • Не путайте с тернарным оператором ? :. Тернарный оператор проверяет любое булево условие, а ?? проверяет только на null.

Ещё примеры для закрепления

// Пример с числами
int? count = null;
int actualCount = count ?? 0; // actualCount = 0

// Пример с объектами
Customer customer = GetCustomerFromDatabase(id);
string displayName = customer?.Name ?? "Неизвестный клиент"; // ?. — это null-conditional operator

// Пример с ??=
List<int> numbers = null;
numbers ??= new List<int>(); // numbers теперь пустой список
numbers.Add(42); // Работает, не будет NullReferenceException

Используйте эти операторы, чтобы сделать код чище и избежать ошибок NullReferenceException. Это один из основных паттернов современного C#.

<<>>

Альтернативный взгляд: когда НЕ стоит использовать ??

Хотя оператор ?? очень удобен, у него есть один нюанс: он всегда вычисляет правую часть, даже если левая часть не равна null. В большинстве случаев это не проблема, но если справа стоит дорогостоящая операция (например, запрос к базе данных), это может ударить по производительности.

Пример плохого использования:

string data = cache.Get("key") ?? FetchDataFromDatabase(); // FetchDataFromDatabase() выполнится ВСЕГДА!

В этом случае FetchDataFromDatabase() будет вызван независимо от того, есть ли данные в кеше. Чтобы избежать этого, используйте старый добрый if:

string data = cache.Get("key");
if (data == null)
{
    data = FetchDataFromDatabase();
}

Или, если вы используете C# 9.0+, можно применить паттерн-матчинг с коротким замыканием:

string data = cache.Get("key") ?? (data = FetchDataFromDatabase()); // Всё равно выполнится, но не так очевидно

Вывод: ?? отлично подходит для простых значений (констант, простых выражений), но для тяжелых вызовов лучше использовать явную проверку на null с помощью if.