C# Типы, допускающие значение NULL

В C#,int、float、bool、DateTimeи т. д. являютсязначимыми типами, они должны иметь значение по умолчанию и не могут бытьnull:

int a = null;  // 编译错误:无法将 null 转换为 int

Типы, допускающие значение NULL,решают эту проблему — они дают значимым типам дополнительное состояние «нет значения» (т. е.,null), что очень полезно в сценариях, где данные могут отсутствовать, например в полях базы данных и возвращаемых значениях API.

Объявление очень простое: добавьте?:

int? a = null;  // 合法
int? b = 42;    // 合法

здесьint?ДаNullable<int>синтаксический сахар (сокращённая форма), эти два варианта полностью эквивалентны:

Nullable&lt;int&gt; a = null;   // 完整写法
int? a = null;              // 简写,效果相同

На следующей диаграмме показано, как типы, допускающие значение NULL, работают в памяти — они используют дополнительный логический флаг, чтобы фиксировать «существует ли значение»:

Memory layout of Nullable<T> int (ordinary value type) Value: 42 4 bytes, always has a value Package Nullable<int> (nullable type) HasValue Value 1 byte 4 bytes 5+ bytes total int? x = 42; true ✓ 42 HasValue = true,Value = 42 int? y = null; false ✗ None HasValue = false, accessing Value will throw an exception.

Разница между одинарным вопросительным знаком ? и двойным вопросительным знаком ??

Два оператора, связанных с типами, допускающими значение null в C#, часто используются вместе, но имеют совершенно разный смысл:

Операторы Name Назначение Пример
? Модификатор типа, допускающего значение null Делает типы значений способными бытьnull int? i = 3;ЭквивалентноNullable<int> i = new Nullable<int>(3);
?? Оператор объединения с null Когда переменнаяnullпредоставить значение по умолчанию, когда int result = i ?? 0;

Простое сравнение:

int i;     // 普通值类型,默认值为 0,永远不能是 null
int? ii;   // 可空类型,默认值为 null

Объявление и присваивание типов, допускающих значение null

Синтаксис объявления

<data_type>? <variable_name> = null;

Например:

int? age = null;
double? temperature = 36.6;
bool? isActive = new bool?();   // 显式构造,默认为 null
DateTime? birthday = null;

Nullable<T>может представлять обычный диапазон своего базового типа значения, плюсnullЗначение. Например,Nullable<int>может быть-2,147,483,648в2,147,483,647любое целое число между ними, илиnull。

Полный пример

Пример

using System;

namespace NullableDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // Объявите переменные разных типов, допускающие значение null
            int? num1 = null;
            int? num2 = 45;
            double? num3 = new double?();   // null
            double? num4 = 3.14157;
            bool? boolVal = new bool?();    // null

            // Отобразить значение (null будет отображаться как пустая строка)
            Console.WriteLine($"num1 = {num1 ?? 0}");         // 0
            Console.WriteLine($"num2 = {num2 ?? 0}");         // 45
            Console.WriteLine($"num3 = {num3 ?? 0.0}");       // 0
            Console.WriteLine($"num4 = {num4 ?? 0.0}");       // 3.14157
            Console.WriteLine($"boolVal = {boolVal}");         // (пусто)
        }
    }
}

Результат вывода:

num1 = 0
num2 = 45
num3 = 0
num4 = 3.14157
boolVal =

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

??оператор используется для предоставлениярезервного значения по умолчанию. Когда левая часть неnullкогда оно не равно null, возвращает левое значение; в противном случае возвращает правое значение:

<表达式1> ?? <表达式2>
  • if<表达式1>неnull, возвращает<表达式1>;
  • иначе возвращает<表达式2>。

Начиная с C# 8.0, также можно использовать??=(оператор присваивания объединения с null), только когда переменнаяnullПрисваивать только когда:

int? x = null;
x ??= 10;   // x 为 null,赋值为 10
x ??= 20;   // x 已有值 10,不再赋值

Пример

using System;

namespace NullableDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            double? num1 = null;
            double? num2 = 3.14157;

            // ?? предоставляет значение по умолчанию
            double result1 = num1 ?? 5.34;   // num1 равен null → возвращает 5.34
            double result2 = num2 ?? 5.34;   // num2 имеет значение → возвращает 3.14157

            Console.WriteLine($"num1 ?? 5.34 = {result1}");   // 5.34
            Console.WriteLine($"num2 ?? 5.34 = {result2}");   // 3.14157

            // Цепочное использование: предоставить несколько альтернативных значений
            int? a = null;
            int? b = null;
            int? c = 42;
            int value = a ?? b ?? c ?? 0;    // Пробуем по порядку, в итоге получаем 42
            Console.WriteLine($"a ?? b ?? c ?? 0 = {value}"); // 42
        }
    }
}

Результат вывода:

num1 ?? 5.34 = 5.34
num2 ?? 5.34 = 3.14157
a ?? b ?? c ?? 0 = 42

Общие свойства и методы типов, допускающих значение null

член Описание Пример
.HasValue Проверить, имеет ли переменная значение (возвращаетbool) if (num.HasValue) { ... }
.Value Получить фактическое значение (если оноnullВызовет исключениеInvalidOperationException) int x = num.Value;
.GetValueOrDefault() Безопасно получить значение; если оноnullВернуть значение по умолчанию для типа (например,0) num.GetValueOrDefault()
.GetValueOrDefault(T) Безопасно получить значение; если оноnullВернуть указанное значение по умолчанию num.GetValueOrDefault(100)
?? Оператор объединения с null (синтаксический сахар, эквивалентенGetValueOrDefault) int result = num ?? 100;

Пример

using System;

namespace NullableDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            int? num = null;

            // HasValue + Value (традиционный подход)
            if (num.HasValue)
                Console.WriteLine($"Value: {num.Value}");
            else
                Console.WriteLine("num has no value");

            // Безопасно получить значение по умолчанию
            Console.WriteLine(num.GetValueOrDefault());      // 0
            Console.WriteLine(num.GetValueOrDefault(99));    // 99
            Console.WriteLine(num ?? 99);                    // 99 (эквивалентная запись)

            // &#x26a0; Примечание: прямое обращение к .Value вызовет исключение
            // int x = num.Value; // InvalidOperationException!
        }
    }
}

Практические сценарии применения

Nullable-типы очень распространены в реальной разработке, особенно при работе с потенциально отсутствующими данными:

Сопоставление полей базы данных

Поля в базе данных могут бытьNULL, nullable-типы могут точно соответствовать:

ID пользователя Age Активен ли
1 28 истина
2 null false
int? age = GetUserAgeFromDB(userId: 2);

// 安全处理 null
string display = age.HasValue
    ? $"用户年龄:{age.Value}"
    : "年龄未知";

// 或者更简洁的写法
string display2 = $"用户年龄:{age ?? 0}";

Необязательные параметры и конфигурация

// 配置项可能未设置
int? timeout = GetConfigValue("timeout");
int actualTimeout = timeout ?? 30;  // 未设置时使用默认值 30 秒

Цепочки проверок на null (C# 6.0+)

// 使用 ?. 运算符安全访问可能为 null 的对象
string? name = user?.Address?.City?.Name;
// 如果 user、Address、City 中任何一个为 null,结果就是 null,不会抛异常

Итог

Особенности Пример Описание
Определение nullable-типов int? x = null; ЭквивалентноNullable<int>
Определение наличия значения x.HasValue returntrueилиfalse
Получить значение (unsafe) x.Value Если он равенnullВызовет исключениеInvalidOperationException
Получить значение по умолчанию x ?? 0 Если он равенnullВернуть значение правой части
Получить указанное значение по умолчанию x.GetValueOrDefault(10) Если он равенnullВернуть 10
Присваивание объединения с null x ??= 10 Только когдаxявляетсяnullприсваивать, когда (C# 8.0+)
Доступ с безопасной навигацией user?.Name ifuserявляетсяnullзатем вернутьnull(C# 6.0+)

«Ссылочные типы, допускающие значение null» в C# 8.0

Начиная с C# 8.0, ... был введён.Ссылочные типы, допускающие значение nullон и тот, что представлен в этой статьеТипы значений, допускающие значение nullпредставляют собой два разных механизма:

Пункт сравнения Типы значений, допускающие значение NULL Ссылочные типы, допускающие значение NULL
Пример int? string?
Целевой объект Тип значения (int、bool、structи т.д.) Ссылочные типы (string、classмассивы и т. д.)
Подход к реализации Во время выполнения с помощьюNullable<T>Реализация структуры Статический анализ компилятора не изменяет поведение во время выполнения
Состояние по умолчанию существовало всегда (начиная с C# 1.0) Необходимо включить в проекте<Nullable>enable</Nullable>
проверка на null использовать.HasValueПринятие решений Компилятор выдаёт предупреждения (не ошибки)

Ссылочные типы, допускающие значение null, — это инструмент проверки безопасности на этапе компиляции: он не изменяет поведение программы во время выполнения, но там, где код может вызватьNullReferenceExceptionпредупреждения компилятора, которые выдаются и помогают выявить потенциальные проблемы на этапе разработки.

Другие расширения