В C# все типы делятся на значимые и ссылочные. Значимые типы int, bool, double, DateTime, структуры - хранятся в стеке и всегда содержат конкретное значение. По умолчанию int равен 0, bool - false, и присвоить им null нельзя. Ссылочные типы, напротив, могут быть null — «ссылка никуда не ведёт».
Проблема возникает при работе с базами данных, API и формами, где значения часто отсутствуют. Если в столбце Age нет данных, чтение его в int делает «нет значения» неотличимым от нуля. Для решения этой проблемы в C# существуют nullable-типы — обёртки над значимыми типами, добавляющие возможность хранить null.
Создание и устройство
Nullable-тип создаётся добавлением вопросительного знака к значимому типу:
int? age = null;
double? price = 99.95;
bool? isEnabled = null;
Под капотом int? — это System.Nullable<int> — структура с двумя полями: значением базового типа и флагом HasValue. Ограничение where T : struct означает, что обернуть можно только значимый тип, а Nullable<Nullable<int>> не существует.
Доступ к значению
Два свойства позволяют работать с nullable-переменной:
int? score = 42;
if (score.HasValue)
Console.WriteLine(score.Value); // 42
Обращение к Value при null выбрасывает InvalidOperationException. Безопасная альтернатива — GetValueOrDefault(), которая возвращает default(T) или заданное значение, не выбрасывая исключений:
int? empty = null;
int r1 = empty.GetValueOrDefault(); // 0
int r2 = empty.GetValueOrDefault(-1); // -1
Преобразование типов
Базовый -> nullable выполняется неявно и без потерь:
int number = 10;
int? nullableNumber = number; // неявно
Nullable -> базовый требует явного приведения и проверки на null:
int? nullableNumber = 42;
int number = (int)nullableNumber; // OK
int? nullableNull = null;
int failed = (int)nullableNull; // исключение!
Между самими nullable-типами работают те же правила, что и между их базовыми типами. Если исходная переменная равна null, результат тоже null.
Особый случай, это упаковка: nullable-переменная со значением null боксится в null, а со значением в свой базовый тип, что упрощает передачу в методы, ожидающие object.
Операции с nullable-типами
Арифметические операторы следуют правилу lifted operators: если хотя бы один операнд null, результат тоже null.
int? a = 10, b = null, c = 5;
int? sum1 = a + c; // 15
int? sum2 = a + b; // null
Операторы сравнения >, <, >=, <= возвращают false, если любой операнд null. Операторы == и != ведут себя иначе: null == null даёт true.
Тип bool? реализует трёхзначную логику:
a b a & b a | b
true null null true
false null false null
null null null null
Оператор null-объединения
Оператор ?? самый частый способ безопасно извлечь значение. Он возвращает левый операнд, если он не null, иначе правый
int? score = null;
int result = score ?? 0; // 0
int value = TryGetFromCache() ?? TryGetFromDb() ?? 0; // цепочка
Начиная с C# 8 оператор ??= присваивает значение только при null:
int? count = null;
count ??= 10; // count теперь 10
Nullable и ссылочные типы
С C# 8 появилась аннотация string? для ссылочных типов. Важно различать: int? реальная структура Nullable<int>, а string? тот же string с пометкой для статического анализатора. Никакой обёртки во втором случае не создаётся.
Nullable-типы элегантный мост между миром значимых типов и реальностью, где данные не всегда есть. Они избавляют от магических чисел-заглушек вроде «-1 означает отсутствие» и делают код точнее отражающим предметную область.
Главное правило при работе с ними всегда осознанно обрабатывать случай null: через ??, HasValue или GetValueOrDefault. Пренебрежение этой проверкой самый частый источник InvalidOperationException и NullReferenceException в коде, использующем nullable-типы.
0 Комментарий(я)
Зарегистрируйтесь чтобы оставить комментарий