Null-совместимые значимые типы
Переменная значимого типа не может принимать null, её содержимым всегда является значение соответствующего типа, поэтому их и называют значимыми. Но такой подход может создавать проблемы. Например, в БД часто бывает ситуация, когда значение представлено целым числом, но оно необязательно. Но CLR не позволяет такого, что может создать проблема при работе с базой данных из .NET Framework.
Чтобы исправить ситуацию, в Microsoft разработали для CLR null-совместимые значимые типы (nullable value types). Они работает с применением определённого в FCL типа System.Nullable<T>. Данный тип является значимым: его экземпляры достаточно производительны, потому что размещаются в стеке, а их размер совпадает с размером исходного типа, к которому прибавляется размер поля типа Boolean. Null-совместимый значимый тип предполагает, что если ему присваивается null, то флаг, отвечающий за наличие значения, становится равен false, а внутренне значение равно дефолтному. Данный класс работает только для значимых типов, так как ссылочные и так могут быть равны null.
Поддержка в C# null-совместимых значимых типов
В настоящее время C# предлагает достаточно удобный синтаксис для работы с null-совместимыми значимыми типами. Переменные можно объявлять и инициализировать прямо в коде, воспользовавшись знаком вопроса после имени типа. При этом можно выполнять преобразования, а также приведения null-совместимых экземпляров к другим типам. Язык C# поддерживает и возможность применения операторов приведения к null-совместимым значимым типам. Вот несколько примеров:
private static void ConversionsAndCasting()
{
// Неявное преобразование из типа Int32 в Nullable<Int32>
Int32? a = 5;
// Неявное преобразование из 'null' в Nullable<Int32>
Int32? b = null;
// Явное преобразование Nullable<Int32> в Int32
Int32 c = (Int32) a;
// Прямое и обратное приведение примитивного типа в null-совместимый тип
Double? d = 5; // Int32->Double? (d содержит 5.0 в виде double)
Double? e = b; // Int32?->Double? (e содержит null)
}
Ещё C# позволяет применять к null-совместимым значимым типам и другие операторы:
private static void Operators()
{
Int32? a = 5;
Int32? b = null;
// Унарные операторы (+ ++ - -- ! ~)
a++; // a = 6
b = -b; // b = null
// Бинарные операторы (+ - * / % & | ^ << >>)
a = a + 3; // a = 9
b = b * 3; // b = null;
// Операторы равенства (== !=)
if (a == null) { /* нет */ } else { /* да */ }
if (b == null) { /* да */ } else { /* нет */ }
if (a != b) { /* да */ } else { /* нет */ }
// Операторы сравнения (<> <= >=)
if (a < b) { /* нет */ } else { /* да */ }
}
Данные операнды C# интерпретирует следующим образом:
- Унарные операторы. Если операнд равен
null, то и результат равенnull. - Бинарные операторы. Если хотя бы один из операндов равен
null, то и результат равенnull. Исключением является применение конъюнкции или дизъюнкции внутри тернарного оператора. Таблицы для этих операторов приведена ниже. - Операторы равенства. Операнды равны если они оба
nullили все их поля совпадают, в противном случае операнды не равны. - Операторы сравнения. Если один из операндов равен
null. тоfalse, иначе значения сравниваются.
Стоит учесть, что для операций с экземплярами null-совместимых значимых типов будет создан больший объём IL-кода в следствие чего, операции будут выполняться медленнее.
Оператор объединения null-совместимых значений
В C# существует оператор объединения null-совместимых значений (null-coalescing operator). Он обозначается как ?? и работает с двумя операндами. Если левый операнд не равен null, оператор возвращает его значение. Иначе возвращается значение правого операнда. Данный оператор удобен при задании значения по умолчанию (прим. А также выбрасывании исключения). Данный оператор работает как с ссылочными, так и с null-совместимыми значимыми типами.
Некоторые считают, что данный оператор является всего лишь синтаксическим сокращением для тернарного оператора. Однако, во-первых, данный оператор лучше работает с выражениями, а во-вторых, он может работать и для большего числа операндов, что повышает читабельность кода.
Поддержка в CLR null-совместимых значимых типов
В CLR существует встроенная поддержка null-совместимых значимых типов. Она предусматривает упаковку и распаковку, а также вызов GetType(), что призвано обеспечить более тесную интеграцию данных типов в CLR. В результате типы ведут себя более естественно и лучше соответствуют ожиданиям разработчиков.
Упаковка null-совместимых значимых типов
При упаковке экземпляра Nullable<T> проверяется его равенство на null и в случае положительного результата возвращается null, в противном случае происходит самая обычная упаковка.
Распаковка null-совместимых значимых типов
В CLR упакованный значимый тип T распаковывается в T или Nullable<T> в зависимости от наличия null в упакованном объекте.
Вызов метода GetType через null-совместимый значимый тип
При вызове метода GetType() для Nullable<T> будет возвращён тип T.
Вызов интерфейсных методов через null-совместимый значимый тип
При приведении null-совместимого значимого типа к интерфейсу код успешно компилируется, несмотря на то, что, например, Nullable<Int32> не реализует IComparable<Int32>. В данном случае механизм верификации CLR считает, что код прошёл проверку, чтобы не приходилось громоздить код сначала приведением к значимому типу, а затем к типу интерфейса.
