Знаете ли вы, как работает null
(и Nullable
) в различных операциях и логических выражениях в C#? Некоторые моменты неочевидны.
Арифметика
Например, null
пожирает остальные значения в арифметических операциях и превращает результат в null
:
1 2 3 4 |
null + null: null null + 1: null 1 + null: null 1 + 1: 2 |
Где это может пригодиться
Не знаю, где это может пригодиться, но это довольно забавно. А если серьезно, то просто имейте это в виду.
Сравнение с числами
Если сравнивать null
с числами, то он одновременно не меньше и не больше их:
1 2 |
null > 0: false null < 2: false |
Где это может пригодиться
Например, у нас есть строка, но мы не знаем, null
она или нет, а нам надо проверить ее длину. В голову может прийти такой неэлегантный вариант:
1 |
if (!string.IsNullOrEmpty(str) && str.Length > 10) { ... } |
Или даже так:
1 |
if (!string.IsNullOrWhiteSpace(str) && str.Length > 10) { ... } |
IsNullOrWhiteSpace
, как и IsNullOrEmpty
— хорошая (отличная!) практика, но в данном случае это излишне, поскольку мы сразу же за этим проверяем длину строки (что нам придется делать в любом случае). Поэтому можно написать проще:
1 |
if (str != null && str.Length > 10) { ... } |
А теперь вспомним наш хак и сократим это еще больше:
1 |
if (str?.Length > 10) { ... } |
Если str == null
, выражение выдаст false
, поскольку str?.Length == null
. Если же строка не null
, будет произведена обычная проверка ее длины.
Пример:
1 2 3 4 5 6 7 8 9 |
public static class Extensions { public static string Truncate(this string str, int length) { return (str?.Length > length) ? str.Substring(0, length) : str; } } |
Сравнение с булевскими величинами
С булевскими значениями все без сюрпризов. null
не равен как true
, так и false
:
1 2 3 4 |
null == false: false null == true: false null != false: true null != true: true |
Где это может пригодиться
null
у нас опять сущность из другого измерения, которая ни с чем не совместима. Но эта особенность позволяет немного элегантнее проверять значения переменных типа bool?
.
Мы довольно часто встречаемся с булевскими полями в сущностях БД, которые могут быть пустыми. Бывают и такие параметры функций.
Например, у нас есть такой код:
1 2 3 4 5 |
using (var model = new Model()) { var project = model.Projects.Where(p => p.Id == 12345); ... } |
Нам нужно проверить, особенный это проект или нет, за это отвечает поле project.IsSpecial
, тип которого bool?
. У него может быть три значения: null
, false
и true
. Первые два соответствуют значению ложь
, и только третье означает истина
, что типично для булевских полей БД, которые могут быть null
.
Таким образом, чтобы узнать, что поле имеет значение истина
, нам нужно убедиться, что оно не пустое и имеет значение true
. Если бы у нас было простое значение bool
, мы бы просто написали:
1 |
if (project.IsSpecial) { ... } |
Но оно у нас Nullable
, поэтому все печально, и нам приходится писать такие монструозные конструкции, как:
1 |
if (project.IsSpecial.HasValue && project.IsSpecial.Value) { ... } |
Или:
1 |
if (project.IsSpecial != null && project.IsSpecial.Value) { ... } |
Что немногим лучше.
Мало того, что нам нужно делать две отдельные проверки, так нам еще два раза приходится писать наименование переменной, которую мы проверяем. В данном случае это не очень страшно, но может быть и хуже:
1 2 |
if (campaign.Project.IsSpecial != null && campaign.Project.IsSpecial.Value) { ... } |
Мало того, что код получается длинным и малочитабельным, так мы еще имеем и повторение кода, которое тоже чревато последствиями. Одним из вариантов будет вынесение campaign.Project
во временную переменную (и я рекомендую так делать для увеличения читабельности кода и избавления от его дублирования), но есть вариант и получше.
Нам на помощь приходит вышеописанный волшебный хак.
1 |
if (project.IsSpecial == true) { ... } |
Вуаля! Элегантно и просто, не правда ли?
Как вариант:
1 |
if (project.IsSpecial ?? false) { ... } |
Результат абсолютно такой же, но этот вариант мне почему-то нравится меньше (наверное, потому что он пришел в голову не мне, а кому-то другому).
Для вложенной структуры все будет выглядеть примерно так же:
1 |
if (campaign?.Project?.IsSpecial == true) { ... } |
Что позволяет не проверять на null
все части пути к финальному полю.
Что же нам делать, если нам нужно проверить значение bool?
на false
? Да примерно то же самое:
1 |
if (project.IsSpecial != true) { ... } |
Обратите внимание, что вот так писать нельзя, потому что значение null
мы тоже считаем ложью
:
1 |
if (project.IsSpecial == false) { ... } |
Сортировка
Мы выяснили, что null
не равен ни true
, ни false
. Но что произойдет, если отсортировать массив значений типа bool?
?
Проверить это очень просто:
1 2 3 4 5 6 7 8 9 |
var list = new bool?[] { true, true, null, false, false, null, true }; foreach (var item in list.OrderBy(i => i)) { Console.WriteLine(item == null ? "null" : item.ToString()); } |
Результат:
1 2 3 4 5 6 7 |
null null False False True True True |
Получается, что null
меньше любого другого значения, причем это не зависит от типа. С числами и строками будет то же самое.