Понятно про null

Что такое null?

Начнем с того что типы в языке С# делятся на ссылочные и типы значений.
Ссылочные типы не хранят в себе само значение, а лишь указывают на него, в отличие от типов значений. Если им не присвоить значение, то они будут указывать на отсутствие значения — null.

Для чего может понадобиться null?

Указание на отсутствие значения может помочь при работе с базами данных, построении клиент серверных-приложений, так как некоторые значения там могут быть необязательны к заполнению. Допустим у нас имеется логин и пароль пользователя, которые обязательны к заполнению, но также есть и электронная почта, которую пользователь может просто не захотеть вводить. В данном случае единственным методов хранить такое значение является указание его отсутствия — использовать NULL.

Почему мы не может использовать null c типом значения?

Как говорилось выше — null указывает на отсутствие значения, но такой подход неприменим для типов значений, т.к. они не указывают на значение, а хранят его, а при отсутствии значения — принимают значение по умолчанию в соответствие со своим типом (например для int — 0, bool — false, char — '\u0000').

Отсутствие значения и ссылочные типы

Существую три основный способа сообщить об отсутствии значения в типах значений(не используя Nullable типы):

  • Использование «магических значений».
    Данный метод предусматривает поиск уникальных значений, которые были бы альтернативой его отсутствию. Для каждого типа нужно указывать собственное магическое значение — как правило это 0, минимальное или максимальное значение, допустимое типом:
DateTime date = DateTime.MinValue; //Присваиваем переменной минимальное значение для DateTime, которое будет эквивалентно пустому значению
if (date == DateTime.MinValue) //Проверяем переменную, на соответствие нашему "магическому значению"
{
Console.WriteLine("Auch!"); //Оу, похоже у нас пустое значение
}
  • Использование отдельного флага типа bool, хранящего в себе информацию пуста ли переменная(false) или имеет значение(true):
DateTime date; //создание самой переменной
bool isValue = false; //создаем флаг - пуста ли переменная data
if (!isValue) //Проверяем, пуста ли переменная
{
Console.WriteLine("Auch!"); //Оу, похоже у нас пустое значение
}
  • Создание отдельной ссылочной обертки, допускающей значение null:
class Time //класс, являющийся оберткой для значения
{
public DateTime value { get; set; } //само значение
}
...
Time time = null; //создание обьекта класса Time
if (time == null) //Проверка на null
{
Console.WriteLine("Auch!"); //Оу, похоже у нас пустое значение
}

Все! Мы решили поставленную задачу и полностью преисполнились в своем познании, но если тщательно обдумать все вышеперечисленные способы, то можно понять, что все они навязывают ненужные контракты программистам и требуют дополнительно документирования, а в некоторых случаях и вовсе могут привести к ошибкам.

Далее мы рассмотрим, как решить эту проблему столь же элегантно, как это присуще языку C#.

Некоторые полезные операторы при работе с null

  • Оператор условного null

Для начала объявим класс, имеющий лишь одну функцию:

class Hello
{
public string text { get; set; }
public void SayHello()
{
Console.WriteLine("Hello {0}", text);
}
}

Теперь создадим «пустой» экземпляр класса User:

Hello newHello = null;

Попытаемся вызвать функцию через объект, но чтобы избежать ошибки, проверим объект на null:

if (newHello != null)
{
newHello.SayHello();
}

Чтобы избежать постоянных проверок на null мы можем заменить этот код всего одной строкой:

newHello?.SayHello();

Оператор .? сначала проверяет на null, а лишь потом вызывает функцию.

Такой же синтаксис можно использовать для безопасного получения значения полей/свойств у объекта:

Console.WriteLine("text = [{0}]", newHello?.text);

Что же мы увидим в консоли? Пустое значение, т.к. значение по факту отсутствует.

  • Оператор объединения с null

Бывают ситуации, когда в случае равенства переменной null нужно вывести значение по умолчанию:

Object number = null;
if (number == null)
{
Console.WriteLine(42);
}
else
{
Console.WriteLine(number);
}

Однако данный синтаксис слишком велик, особенное если у нас 10 таких переменных.
Попробуем заменить это нагромождение кода тернарным оператором:

Console.WriteLine(number == null ? 42 : number);

Стало значительно лучше, но с помощью оператора объединения с null можно это сократить код еще больше:

Console.WriteLine(number ?? 42);

Данный оператор имеет следующий синтаксис: переменная ?? значение по умолчанию.

Nullable

Начнем с того, что Nullable является обобщенной структурой, находящейся в пространстве имен System. Он выглядит следующим образом:

public partial struct Nullable<T> where T : struct

Исходя из объявления(where T : struct) можно понять что он принимает универсальные параметры только типа значения.

Создадим переменную Nullable с параметром int:

Nullable<int> number = 42;

Однако существует более лаконичный синтаксис:

int? number = 42;

Попробуем вывести это значение с помощью свойства Value, которое хранит значение объекта:

Console.WriteLine(number.Value); //Получаем 42

Как видим, number ведет себя аналогично типам значений, однако теперь мы можем присвоить ему null:

int? number = null;
if (number == null)
{
Console.WriteLine("Auch!"); //похоже у нас null
}

Такой же результат можно получить с помощью свойства hasValue, которое возвращает false, если переменная не хранит значение и false, если хранит:

if (!number.HasValue)
{
Console.WriteLine("Auch!"); //у нас все еще null
}

Также тип Nallable определяет метод GetValueOrDefault, который аналогичен оператору объединения с null:

Console.WriteLine(number.GetValueOrDefault()); //получим 0, т.к. это значение по умолчанию для типа int
Console.WriteLine(number.GetValueOrDefault(42)); //получим 42, т.к. мы указали его как значение по умолчанию
Console.WriteLine(number ?? 42); //тоже самое, только с оператором "объединения с null”

Преобразования с Nullable

Начнем с явного преобразования из Nullable в реальный тип:

int? a = 1;
int b = (int)a;
Console.WriteLine(b); //1

Все будет работать. Однако попробовав совершить тоже самое действие с null мы получим ошибку:

int? a = null;
int b = (int)a;
Console.WriteLine(b); //Ошибка

Исправить это можно функцией GetValueOrDefault(или оператором объединения с null):

int? a = null;
int b = a.GetValueOrDefault(); //0
int c = a ?? 0; //0

С преобразование из реального типа в null все гораздо проще:

int a = 0;
int? c = a; //0

Выводы

Говоря о преимуществах null, нельзя забывать о том, что он с легкость может привести к ошибкам, связанным с попыткой использования пустого объекта (всеми любимая ошибка NullFeferenceException). Такая ситуация может возникнуть в результате использования вашего кодом другим программистом, особенно если вы не уведомите его о том, что такое знание допустимо в данной ситуации.

364 views·5 shares