1. Оператор объединения с null (??)
Ваши знания о nullable-типах не будут полными без умения писать лаконичные и безопасные конструкции. В C# для этого есть два суперполезных оператора: объединение с null (??) и null-условный оператор (?.).
Оператор ?? позволяет задать “значение по умолчанию” для переменной, которая может быть null.
Представьте, что у вас есть имя пользователя, которое может быть null. И если оно действительно null, вы хотите отобразить "Гость". Пример:
string userName = null;
string displayName = userName != null ? userName : "Гость";
Оператор ?? позволяет записать это компактнее:
string userName = null;
string displayName = userName ?? "Гость";
Как это работает:
- Если выражение слева не null, оно возвращается как результат.
- Если слева null, используется значение справа.
Ещё примеры:
int? age = null;
int displayAge = age ?? -1; // -1 — значение по умолчанию
Console.WriteLine(displayAge); // Выведет -1
string input = null;
string name = input ?? "Гость";
Console.WriteLine($"Привет, {name}!"); // Привет, Гость!
Цепочки операторов
Можно вкладывать ?? в цепочку:
string result = str1 ?? str2 ?? "По умолчанию";
Если str1 не null, используется он. Иначе — str2, если и он null — тогда строка "По умолчанию".
2. Null-условный оператор (?.)
Ещё одна частая ситуация — вызвать метод или обратиться к свойству объекта, только если сам объект не null. Пример:
User user = null;
string displayName = user != null ? user.Name : null;
Оператор ?. позволяет записать это проще:
User user = null;
string displayName = user?.Name;
Если объект user равен null, выражение не вызовет ошибку, а просто вернёт null.
Примеры:
User user = null;
// Без ?. — будет ошибка
// Console.WriteLine(user.Name); // NullReferenceException
Console.WriteLine(user?.Name); // Безопасно — напечатает пустую строку или ничего
Console.WriteLine(user?.GetProfileInfo()); // Аналогично
User[] users = null;
int? count = users?.Length; // null, если users == null
Цепочки операторов
Можно строить целые "цепочки":
string domain = company?.Director?.Email?.Split('@')?[1];
Если любая часть пути окажется null, выражение вернёт null, а не выбросит исключение.
Сочетание с ??
Операторы ?. и ?? отлично работают вместе:
string display = user?.Name ?? "Неизвестно";
Если user или user.Name равны null, будет выведено "Неизвестно".
3. Что такое ! и зачем он нужен
Оператор подавления предупреждений о null
В современных версиях C# (при включённом NRT) компилятор заботливо предупреждает, если вы обращаетесь к переменной, которая может быть null. Но иногда вы уверены, что всё под контролем. Тогда используется оператор подавления !.
string? possibleNull = GetUserNameMaybeNull();
Console.WriteLine(possibleNull.Length); // Warning: возможен null
Console.WriteLine(possibleNull!.Length); // Компилятор молчит, но если там null — будет исключение
Важно: ! не защищает от ошибок — он просто говорит компилятору "поверь мне". Если переменная действительно равна null, будет NullReferenceException.
Куда не стоит его применять
Не используйте ! вслепую. Хороший стиль — минимизировать его применение. Лучше перестроить код, чтобы null был либо невозможен, либо явно обработан.
4. default — как получить "стандартное" значение типа
Иногда нужно просто "сбросить" переменную в начальное состояние, особенно если не хочется вручную писать 0, false или null.
Для этого существует ключевое слово default.
int a = default; // a == 0
bool flag = default; // flag == false
string s = default; // s == null
double? d = default; // d == null
В вашем мини-приложении можно, например, сбросить имя или возраст:
userName = default; // null
userAge = default; // null (если userAge — int?)
5. Различия и нюансы: ?, !, default
Даже опытные разработчики иногда теряются, когда видят ?, ! и default рядом. Разберёмся, кто есть кто.
? — вы разрешаете null
- Для значимых типов: int? x — теперь x может быть null.
- Для ссылочных типов: string? s — явно указываете, что переменная может быть null (в режиме NRT).
! — вы утверждаете, что null не будет
- user!.Name — вы обещаете компилятору, что user точно не null.
- Работает только в режиме Nullable Reference Types.
default — вы просите значение "по умолчанию"
- int x = default; — x станет 0
- string s = default; — s станет null
Сравнение:
| Синтаксис | Что это? | Где работает? | При чём тут null? |
|---|---|---|---|
|
Nullable значимый тип | Везде | Можно присвоить null |
|
Nullable ссылочный тип (NRT) | В режиме NRT | Можно присвоить null |
|
Null-forgiving operator | В режиме NRT | Подавляет предупреждение |
|
Значение по умолчанию | Везде | Для ссылочных — это null |
6. Типичные ошибки при использовании ?, ! и default
Ошибка №1: считают, что ! "исцеляет" null.
На самом деле ! просто заставляет компилятор не ругаться. Если значение всё-таки окажется null, программа упадёт с NullReferenceException.
Ошибка №2: забывают проверять .HasValue перед использованием .Value.
Особенно это касается int?, bool? и других nullable-типов. Без такой проверки можно получить исключение InvalidOperationException.
Ошибка №3: используют default там, где нужен "особый" нулевой смысл.
Например, 0 или false могут быть допустимыми значениями, а не сигналами "пустоты". Это может привести к логическим ошибкам.
Ошибка №4: не используют ? для ссылочных типов в режиме NRT — или злоупотребляют им.
Кто-то забывает про ?, и компилятор выдаёт ворох предупреждений. А кто-то, наоборот, везде лепит ?, даже там, где null никогда не бывает. И то, и другое снижает читаемость и надёжность кода.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ