1. Введение
Давайте представим себе такую ситуацию. Вы храните данные о котах в коде. И вам хочется кроме, например, возраста кота, хранить еще состояния наподобие "нет данных", "возраст неизвестен". Можно было бы хранить 0, но нуль — это вполне себе валидный возраст.
int age = 0; // Возраст кота. Но что такое 0? Это котенок или "неизвестно"?
Проблема в том, что int принимает целые значения, а "нет данных" среди целых чисел не предусмотрено. Если же возраст — строка, там можно было бы присвоить null. Но с числами такой фокус не проходит:
int age = null; // Ошибка! Нельзя присвоить null переменной типа int
Это связано с тем, что значимые типы (struct-ы вроде int, double, DateTime, bool) всегда что-то хранят. У них нет "пустого" состояния (в отличие от ссылочных типов, где null — это всего лишь отсутствие объекта).
Пример из жизни:
Пытаясь обойти это ограничение, программисты иногда придумывают "особые" значения: например, -1 или int.MaxValue для "нет значения". Но это некрасиво, неудобно и опасно: легко перепутать реальное значение с заглушкой.
2. Nullable-тип: тип, у которого есть "null"
Идея
А что, если позволить обычным числам (и вообще всем значимым типам) хранить не только значение, но и null?
В C# это реализовано с помощью специального класса-обёртки Nullable<T>. Такой класс может содержать либо значение типа T, либо ничего (то есть null).
Синтаксис
int? age = null; // Переменная age может быть и числом, и null
Компилятор C#, когда встречает такой код, фактически воспринимает его как:
Nullable<int> age = new Nullable<int>(); // Переменная age не содержит значения
Или, если нужно явно указать значение:
Nullable<int> age = new Nullable<int>(42); // age содержит число 42
Таким образом, знак вопроса превращает любой значимый тип в его nullable-версию:
- int? (то же самое, что Nullable<int>)
- double?
- DateTime?
- и любые другие структуры
Nullable-обёртки для самых популярных типовых структур
| Обычный тип | Nullable-тип | Пример записи |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Можно записать и длиннее, используя класс-обёртку:
Nullable<int> pages = null;
Но писать int? — короче, а значит, круче.
3. Как использовать nullable-тип
Присваивание и проверка
Присваивание null работает так же, как и с ссылочными типами:
Объявление и присваивание
int? temperature = null;
temperature = 25;
Проверка на null
Как узнать, есть ли в нашем nullable-типе настоящее значение?
if (temperature != null)
{
Console.WriteLine($"Температура: {temperature}");
}
else
{
Console.WriteLine("Нет данных о температуре");
}
Свойства Nullable-типов: .HasValue и .Value
У nullable-типов есть два важных свойства:
- .HasValue — возвращает true, если в переменной что-то лежит (не null).
- .Value — само значение, если оно есть (иначе будет исключение).
int? temperature = null;
if (temperature.HasValue) //исключения не будет!
{
Console.WriteLine($"Температура: {temperature.Value}°C");
}
else
{
Console.WriteLine("Температура неизвестна");
}
Этот код будет работать: temperature не равно null, оно содержит null внутри. И вы всегда можете вызвать у Nullable типа свойство HasValue. А вот пытаться получить .Value, когда его нет — плохая идея, получите исключение InvalidOperationException. Проверяйте наличие значения заранее!
Сокращённый синтаксис вывода
Во многих случаях достаточно просто использовать переменную nullable-типа — и C# сам всё поймёт:
int? hours = null;
Console.WriteLine(hours); // ничего не выводит (просто пусто)
hours = 10;
Console.WriteLine(hours); // выводит 10
4. Операции и nullable-типы: арифметика, сравнения, преобразования
Операции: что происходит?
Если один из операндов — nullable, результат тоже nullable.
А если один из операндов равен null, результат тоже null.
int? a = 5;
int? b = null;
int? sum = a + b; // sum == null
int? c = 10;
int? d = 15;
int? total = c + d; // total == 25
Операции сравнения
Можно сравнивать nullable-типы с обычными числами:
int? score = null;
if (score > 0) Console.WriteLine("Отличный результат!");
else Console.WriteLine("Нет результата"); // Сработает эта ветка
Если score равен null, условие score > 0 будет false.
Явное и неявное преобразование
- Из обычного типа в nullable: автоматически.
- Из nullable в обычный: только если не null (иначе будет исключение).
int? x = 3;
int y = x.Value; // Ok, если x не null
// Неявно
int? z = y;
5. Nullable и методы: параметры и возвращаемые значения
Возвращение nullable-значений
Если метод не всегда может вернуть результат, используйте nullable-типы как возвращаемое значение:
// Метод ищет пользователя по логину и возвращает его возраст, если нашёл, иначе null
int? FindUserAge(string login)
{
// ... тут логика поиска
return null; // если не найден
}
Nullable-параметры
Можно принимать nullable-значения в параметры, чтобы явно дать понять: “может быть, а может и не быть”.
void PrintTemperature(int? temp)
{
if (temp == null)
Console.WriteLine("Температура неизвестна");
else
Console.WriteLine($"Температура: {temp} градусов");
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ