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

Тип значений, допускающий значение NULL, или T?, представляет все значения своего базового типа значения T, а также дополнительное значение NULL. Например, можно присвоить переменной bool? любое из следующих трех значений: true, false или null. Базовый тип значения T не может соответствовать типу значения, допускающему значение NULL.

Типы, допускающие значение NULL, представляют собой экземпляры универсальной структуры System.Nullable<T>. Вы можете ссылаться на тип значения, допускающий значение NULL, с базовым типом T в любой из следующих взаимозаменяемых форм: Nullable<T> или T?.

Тип значения, допускающий значение NULL, следует использовать, когда нужно представить неопределенное значение его базового типа. Например, логическая переменная (или bool) может иметь только значения true или false. Однако в некоторых приложениях значение переменной может быть неопределенным или отсутствовать. Например, поле базы данных может содержать значение true или false либо вообще никакого значения, то есть NULL. В этом сценарии можно использовать тип bool?.

Назначение и объявление

Так как тип значения можно неявно преобразовать в соответствующий тип значения, допускающий значение NULL, вы назначаете значение переменной такого типа значения так же, как для базового типа значения. Вы можете также присвоить значение null. Например:

double? pi = 3.14;
char? letter = 'a';

int m2 = 10;
int? m = m2;

bool? flag = null;

// An array of a nullable value type:
int?[] arr = new int?[10];

Значение по умолчанию для типа значения, допускающего значение NULL, представляет null, то есть это экземпляр, свойство Nullable<T>.HasValue которого возвращает false.

Проверка экземпляра типа значения, допускающего значение NULL

Оператор можно использовать is с шаблоном типа, чтобы изучить экземпляр типа null , допускающего значение NULL, и получить значение базового типа:

int? a = 42;
if (a is int valueOfA)
{
    Console.WriteLine($"a is {valueOfA}");
}
else
{
    Console.WriteLine("a does not have a value");
}
// Output:
// a is 42

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

  • Nullable<T>.HasValue указывает, имеет ли экземпляр типа, допускающего значение NULL, значение своего базового типа.

  • Nullable<T>.Value возвращает значение базового типа, если HasValue имеет значение true. Если HasValue имеет значение false, свойство Value выдает исключение InvalidOperationException.

В следующем примере используется свойство HasValue, чтобы проверить, содержит ли переменная значение, перед его отображением:

int? b = 10;
if (b.HasValue)
{
    Console.WriteLine($"b is {b.Value}");
}
else
{
    Console.WriteLine("b does not have a value");
}
// Output:
// b is 10

Можно также сравнить переменную типа значения, допускающего значение NULL, с null вместо использования свойства HasValue, как показано в следующем примере:

int? c = 7;
if (c != null)
{
    Console.WriteLine($"c is {c.Value}");
}
else
{
    Console.WriteLine("c does not have a value");
}
// Output:
// c is 7

Преобразование из типа значения, допускающего значение NULL, в базовый тип

Если необходимо присвоить значение типа, допускающего значение NULL, переменной типа значения, не допускающего значения NULL, может потребоваться указать значение, назначаемое вместо null. Для этого используйте оператор объединения со значением NULL ?? (можно также применить метод Nullable<T>.GetValueOrDefault(T) для той же цели):

int? a = 28;
int b = a ?? -1;
Console.WriteLine($"b is {b}");  // output: b is 28

int? c = null;
int d = c ?? -1;
Console.WriteLine($"d is {d}");  // output: d is -1

Если вы хотите использовать значение по умолчанию базового типа значения вместо null, воспользуйтесь методом Nullable<T>.GetValueOrDefault().

Вы можете также явно привести тип значения, допускающий значение NULL, к типу, не допускающему значение NULL, как показано в примере ниже.

int? n = null;

//int m1 = n;    // Doesn't compile
int n2 = (int)n; // Compiles, but throws an exception if n is null

Во время выполнения, если значение типа значения, допускающего значение NULL, равно null, явное приведение вызывает исключение InvalidOperationException.

Тип T, не допускающий значение NULL, неявно преобразуется в соответствующий тип, допускающий значение NULL, T?.

Операторы с нулификацией

Предопределенные унарные и бинарные операторы или любые перегруженные операторы, поддерживаемые типом значения T, также поддерживаются соответствующим типом значения, допускающим значение NULL, т. е. T?. Эти операторы, также называемые операторами с нулификацией, возвращают значение null, если один или оба операнда имеют значение null. В противном случае оператор использует содержащиеся значения операндов для вычисления результата. Рассмотрим пример.

int? a = 10;
int? b = null;
int? c = 10;

a++;        // a is 11
a = a * c;  // a is 110
a = a + b;  // a is null

Примечание.

Для типа bool? предопределенные операторы & и | не следуют правилам, описанным в этом разделе: результат вычисления оператора может быть отличным от NULL, даже если один из операндов имеет значение null. См. подробнее о логических операторах, поддерживающих значение NULL в описании логических операторов.

Для операторов сравнения <, >, <= и >=, если один или оба операнда равны null, результат будет равен false. В противном случае сравниваются содержащиеся значения операндов. Тут важно не полагать, что если какая-то операция сравнения (например, <=) возвращает false, то противоположное сравнение (>) обязательно вернет true. В следующем примере показано, что 10

  • не больше и не равно значению null,
  • не меньше чем null.
int? a = 10;
Console.WriteLine($"{a} >= null is {a >= null}");
Console.WriteLine($"{a} < null is {a < null}");
Console.WriteLine($"{a} == null is {a == null}");
// Output:
// 10 >= null is False
// 10 < null is False
// 10 == null is False

int? b = null;
int? c = null;
Console.WriteLine($"null >= null is {b >= c}");
Console.WriteLine($"null == null is {b == c}");
// Output:
// null >= null is False
// null == null is True

Для оператора равенства ==, если оба операнда равны null, результат будет равен true. Если один из операндов равен null, результат будет равен false. В противном случае сравниваются содержащиеся значения операндов.

Для оператора неравенства !=, если оба операнда равны null, результат будет равен false. Если один из операндов равен null, результат будет равен true. В противном случае сравниваются содержащиеся значения операндов.

Если между двумя типами данных определено пользовательское преобразование типов, то это же преобразование можно также использовать между соответствующими типами, допускающими значение NULL.

Упаковка-преобразование и распаковка-преобразование

Экземпляр типа значения, допускающего значение NULL, T?упакован следующим образом:

  • Если HasValue возвращает false, создается пустая ссылка.
  • Если HasValue возвращает true, упаковывается соответствующее значение базового типа T, а не экземпляр Nullable<T>.

Можно распаковать упакованный тип значения T в соответствующий тип, допускающий значение NULL, T?, как показано в следующем примере:

int a = 41;
object aBoxed = a;
int? aNullable = (int?)aBoxed;
Console.WriteLine($"Value of aNullable: {aNullable}");

object aNullableBoxed = aNullable;
if (aNullableBoxed is int valueOfA)
{
    Console.WriteLine($"aNullableBoxed is boxed int: {valueOfA}");
}
// Output:
// Value of aNullable: 41
// aNullableBoxed is boxed int: 41

Идентификация типа значений, допускающего значение NULL

В следующем примере показано, как определить, представляет ли экземпляр System.Type сконструированный тип значений, допускающий значение NULL, т. е. тип System.Nullable<T> с указанным параметром типа T:

Console.WriteLine($"int? is {(IsNullable(typeof(int?)) ? "nullable" : "non nullable")} value type");
Console.WriteLine($"int is {(IsNullable(typeof(int)) ? "nullable" : "non-nullable")} value type");

bool IsNullable(Type type) => Nullable.GetUnderlyingType(type) != null;

// Output:
// int? is nullable value type
// int is non-nullable value type

Как показано в примере, при помощи оператора typeof можно создать экземпляр System.Type.

Если вы хотите определить, принадлежит ли экземпляр к типу значений, допускающему значение NULL, не используйте метод Object.GetType для получения экземпляра Type для тестирования с помощью приведенного выше кода. При вызове метода Object.GetType в экземпляре типа значений, допускающего значение NULL, экземпляр упаковывается в Object. Так как упаковка экземпляра типа значений, допускающего значение NULL, значение которого отлично от NULL, эквивалентна упаковке значения базового типа, GetType возвращается экземпляр Type, представляющий базовый тип типа значений, допускающего значение NULL:

int? a = 17;
Type typeOfA = a.GetType();
Console.WriteLine(typeOfA.FullName);
// Output:
// System.Int32

Кроме того, не используйте оператор is, чтобы определить, принадлежит ли экземпляр к типу значений, допускающему значение NULL. Как показано в следующем примере, вы не можете отличить типы экземпляра типа значений, допускающего значение NULL, и его экземпляра базового типа с помощью оператора is:

int? a = 14;
if (a is int)
{
    Console.WriteLine("int? instance is compatible with int");
}

int b = 17;
if (b is int?)
{
    Console.WriteLine("int instance is compatible with int?");
}
// Output:
// int? instance is compatible with int
// int instance is compatible with int?

Вместо этого используйте Nullable.GetUnderlyingType из первого примера и оператора typeof , чтобы проверить, имеет ли экземпляр тип значения NULL.

Примечание.

Методы, описанные в этом разделе, неприменимы в случае ссылочных типов, допускающих значения NULL.

Спецификация языка C#

Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:

См. также