Null и значимые типы

Последнее обновление: 11.11.2021

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

Чтобы присвоения переменной или параметру значимого типа значения null, эти переменная/параметр значимого типа должны представлять тип nullable. Для этого после названия типа указывается знак вопроса ?

int? val = null;
Console.WriteLine(val);

Здесь переменная val представляет не просто тип int, а тип int? - тип, переменные/параметры которого могут принимать как значения типа int, так и значение null. В данном случае мы передаем ей значение null. Но также можно передать и значение типа int:

int? val = null;
IsNull(val);    // null
val = 22;
IsNull(val);    // 22

void IsNull(int? obj)
{
    if (obj == null) Console.WriteLine("null");
    else Console.WriteLine(obj);
}

Однако если переменная/параметр представляет значимый не nullable-тип, то присвоить им значение null не получится:

int val = null;  // ! ошибка, переменная val НЕ представляет тип nullable 

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

int? number1 = 5;
Nullable<int> number2 = 5;

Свойства Value и HasValue и метод GetValueOrDefault

Структура Nullable<T> имеет два свойства:

  • Value - значение объекта

  • HasValue: возвращает true, если объект хранит некоторое значение, и false, если объект равен null.

Мы можем использовать эти свойства для проверки наличия и получения значения:

PrintNullable(5);       // 5
PrintNullable(null);    // параметр равен null

void PrintNullable(int? number)
{
    if (number.HasValue)
    {
        Console.WriteLine(number.Value);
        // аналогично
        Console.WriteLine(number);
    }
    else
    {
        Console.WriteLine("параметр равен null");
    }
}

Однако если мы попробуем получить через свойство Value значение переменной, которая равна null, то мы столкнемся с ошибкой:

int? number = null;
Console.WriteLine(number.Value);	// ! Ошибка
Console.WriteLine(number);			// Ошибки нет - просто ничего не выведет

Также структура Nullable<T> имеет метод GetValueOrDefault(). Он возвращает значение переменной/параметра, если они не равны null. Если они равны null, то возвращается значение по умолчанию. Значение по умолчанию можно передать в метод. Если в метод не передается данных, то возвращается значение по умолчанию для данного типа данных (например, для числовых данных это число 0).

int? number = null; // если значения нет, метод возвращает значение по умолчанию
Console.WriteLine(number.GetValueOrDefault());      // 0  - значение по умолчанию для числовых типов
Console.WriteLine(number.GetValueOrDefault(10));    // 10

number = 15;    // если значение задано, оно возвращается методом
Console.WriteLine(number.GetValueOrDefault());    // 15
Console.WriteLine(number.GetValueOrDefault(10));  // 15

Преобразование значимых nullable-типов

Рассмотрим возможные преобразования:

  • явное преобразование от T? к T

    int? x1 = null;
    if(x1.HasValue)
    {
        int x2 = (int)x1;
        Console.WriteLine(x2);
    }
    
  • неявное преобразование от T к T?

    int x1 = 4;
    int? x2 = x1;
    Console.WriteLine(x2);
    
  • неявные расширяющие преобразования от V к T?

    int x1 = 4;
    long? x2 = x1;
    Console.WriteLine(x2);
    

    явные сужающие преобразования от V к T?

    long x1 = 4;
    int? x2 = (int?)x1;
    
  • Подобным образом работают явные сужающие преобразования от V? к T?

    long? x1 = 4;
    int? x2 = (int?)x1;
    
  • явные сужающие преобразования от V? к T

    long? x1 = null;
    if (x1.HasValue) 
    { 
        int x2 = (int)x1; 
    }
    

Операции с nullable-типами

nullable-типы поддерживают тот же набор операций, что и их не-nullable двойники. Но следует учитывать, что если в операции участвует nullable-тип, то результатом также будет значение nullable-типа

int? x = 5;
int z = x + 7;          // нельзя
int? w = x + 7;         // можно
int d = x.Value + 7;    // можно

В арифметических операциях, если один из операндов равен null, то результатом операции также будет null:

int? x = null;
int? w = x + 7;         // w = null

В операциях сравнения >, <, >= и <=, если хотя бы один из операндов равен null, то возвращается false (кроме операции !=):

int? x = null;
int? y = 5;     
int? z = null;  
Console.WriteLine($"x > y is {x > y}");     // false
Console.WriteLine($"x < y is {x < y}");     // false 
Console.WriteLine($"x >= y is {x >= y}");   // false
Console.WriteLine($"x <= y is {x <= y}");   // false

Console.WriteLine($"x > z is {x > z}");     // false
Console.WriteLine($"x < z is {x < z}");     // false 
Console.WriteLine($"x >= z is {x >= z}");   // false
Console.WriteLine($"x <= z is {x <= z}");   // false

В операциях же == и != идет стандартное сравнение:

int? x = null;
int? y = 5;     
int? z = null;  

Console.WriteLine($"x == y is {x == y}");   // false
Console.WriteLine($"x != y is {x != y}");   // true

Console.WriteLine($"x == z is {x == z}");   // true
Console.WriteLine($"x != z is {x != z}");   // false
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850