что такое nullable тип

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

В C# 8.0 появилась возможность использования ссылочных типов, допускающих значение NULL. Дополнительные сведения см. в статье Ссылочные типы, допускающие значение NULL. Типы значений, допускающие значение NULL, доступны начиная с C# 2.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Источник

Nullable Структура

Определение

Некоторые сведения относятся к предварительной версии продукта, в которую до выпуска могут быть внесены существенные изменения. Майкрософт не предоставляет никаких гарантий, явных или подразумеваемых, относительно приведенных здесь сведений.

Параметры типа

Примеры

В следующем примере кода определяются три строки таблицы в образце базы данных Microsoft Pubs. Таблица содержит два столбца, которые не допускают значение null, и два столбца, допускающие значение null.

Комментарии

Nullable Структура поддерживает использование только типа значения в качестве типа, допускающего значение null, так как ссылочные типы допускают значение null при проектировании.

NullableКласс предоставляет дополнительную поддержку для Nullable структуры. NullableКласс поддерживает получение базового типа типа, допускающего значение null, а также операции сравнения и равенства с парами типов, допускающих значение null, базовый тип которых не поддерживает универсальные операции сравнения и равенства.

Фундаментальные свойства

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

Конструкторы

Инициализирует новый экземпляр структуры Nullable заданным значением.

Свойства

Возвращает значение, указывающее, имеет ли текущий объект Nullable допустимое значение своего базового типа.

Методы

Указывает, равен ли текущий объект Nullable указанному объекту.

Извлекает хэш-код объекта, возвращенного свойством Value.

Извлекает значение текущего объекта Nullable или значение базового типа этого объекта по умолчанию.

Извлекает значение текущего объекта Nullable или заданное значение по умолчанию.

Операторы

Определяет явное преобразование экземпляра Nullable в его базовое значение.

Источник

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

Синтаксис

Объявление и присваивание со значениями

Объявление типа значения, допускающего значение null, аналогично объявлению любого типа, подобного оболочке, в F #:

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

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

Присвоить значение null

Нельзя напрямую присвоить null тип значения, допускающего значение null. Nullable() Вместо этого используйте:

Это вызвано тем, что Nullable имеет null неправильное значение.

Передача и назначение членам

Основное различие между работой с членами и значениями F # заключается в том, что типы значений, допускающие значение null, могут быть неявно выведены при работе с членами. Рассмотрим следующий метод, принимающий тип значения Nullable в качестве входных данных:

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

В отличие от параметров, которые являются обобщенной конструкцией для представления возможного значения, типы значений, допускающие значение null, не используются при сопоставлении шаблонов. Вместо этого необходимо использовать if выражение и проверить HasValue свойство.

Чтобы получить базовое значение, используйте Value свойство после HasValue проверки, например так:

Операторы, допускающие значение NULL

Операции с типами значений, допускающими значения NULL, такие как арифметическое или сравнение, могут потребовать использования операторов, допускающих значение NULL.

Можно преобразовать из одного типа значений, допускающего значение null, в другой с помощью операторов преобразования из FSharp.Linq пространства имен:

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

Можно также использовать операторы Nullable в качестве короткого оператора для проверки HasValue и Value :

?> Сравнение проверяет, является ли левая часть значения NULL, и только если она имеет значение. Он эквивалентен строке, следующей за ней.

Источник

Ссылочные типы, допускающие значение NULL (справочник по C#)

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

Ссылочные типы, допускающие значение NULL, доступны начиная с C# 8.0, в коде, который дал явное согласие на контекст, поддерживающий значение NULL. Ссылочные типы, допускающие значение NULL, предупреждения о значении NULL при статическом анализе и оператор, опускающий NULL, являются необязательными функциями языка. По умолчанию все они отключены. Контекст, допускающий значение NULL, контролируется на уровне проекта с помощью параметров сборки или в коде с помощью директив pragma.

В контексте, поддерживающем значение NULL:

Переменные notNull и nullable представлены типом String. Так как типы, допускающие и не допускающие значение NULL, хранятся в виде одного типа, существует несколько мест, где использование ссылочного типа, допускающего значение NULL, не допускается. Как правило, ссылочный тип, допускающий значение NULL, запрещено использовать в качестве базового класса или реализованного интерфейса. Ссылочный тип, допускающий значение NULL, не может использоваться в выражении проверки типа или создания объекта. Ссылочный тип, допускающий значение NULL, не может быть типом выражения доступа к члену. Эти конструкции показаны в следующих примерах:

Ссылки, допускающие значение NULL, и статический анализ

Примеры в предыдущем разделе иллюстрируют природу ссылочных типов, допускающих значение NULL. Ссылочные типы, допускающие значение NULL, не являются новыми типами классов, а обозначены заметками для существующих ссылочных типов. Компилятор использует эти заметки, чтобы помочь найти потенциальные ошибки для пустых ссылок в коде. Во время выполнения нет никакой разницы между ссылочным типом, не допускающим значение NULL, и ссылочным типом, допускающим значение NULL. Компилятор не добавляет никакую проверку для ссылочных типов, не допускающих значение NULL, во время выполнения. Преимущества заключаются в анализе времени компиляции. Компилятор создает предупреждения, помогающие находить и исправлять потенциальные ошибки со значениями NULL в коде. Вы объявляете свое намерение, и компилятор предупреждает вас, если код нарушает его.

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

Ссылочные типы, не допускающие значение NULL, всегда должны быть безопасными для разыменования, так как их состоянием NULL является не равно NULL. Чтобы применить это правило, компилятор выдает предупреждения, если ссылочный тип, не допускающий значение NULL, не инициализируется со значением, отличным от NULL. Локальные переменные должны присваиваться там же, где они объявляются. Каждому полю должно быть присвоено значение, не равное NULL, в инициализаторе поля или в каждом конструкторе. Компилятор выдает предупреждения, если ссылка, не допускающая значение NULL, присваивается ссылке с состоянием может быть NULL. В целом, так как ссылка, не допускающая значение NULL, имеет состояние не равно NULL, при разыменовании этих переменных предупреждения не выдаются.

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

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

Задание контекста, допускающего значение NULL

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

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

Источник

Хорошо ли вы помните nullable value types? Заглядываем «под капот»

что такое nullable тип. Смотреть фото что такое nullable тип. Смотреть картинку что такое nullable тип. Картинка про что такое nullable тип. Фото что такое nullable тип

В последнее время модной темой стали nullable reference types. Однако старые добрые nullable value types никуда не делись и всё так же активно используются. Хорошо ли вы помните нюансы работы с ними? Предлагаю освежить или проверить свои знания, ознакомившись с этой статьёй. Примеры кода на C# и IL, обращения к спецификации CLI и коду CoreCLR прилагаются. Начать предлагаю с интересной задачки.

Примечание. Если вас интересуют nullable reference types, можете познакомиться с несколькими статьями моих коллег: «Nullable Reference типы в C# 8.0 и статический анализ», «Nullable Reference не защищают, и вот доказательства».

Посмотрите на пример кода ниже, и ответьте, что будет выведено в консоль. И, что не менее важно, почему. Только давайте сразу договоримся, что вы ответите, как есть: без подсказок компиляторов, документации, вычитывания литературы или чего-то подобного. 🙂

что такое nullable тип. Смотреть фото что такое nullable тип. Смотреть картинку что такое nullable тип. Картинка про что такое nullable тип. Фото что такое nullable тип

Что ж, давайте немного порассуждаем. Возьмём несколько основных направлений мысли, которые, как мне кажется, могут возникнуть.

1. Исходим из того, что int? — ссылочный тип.

Давайте рассуждать так, что int? — это ссылочный тип. В таком случае в а будет записано значение null, оно же будет записано и в aObj после присвоения. В b будет записана ссылка на какой-то объект. Она также будет записана и в bObj после присвоения. В итоге, Object.ReferenceEquals примет в качестве аргументов значение null и ненулевую ссылку на объект, так что…

Всё очевидно, ответ — False!

2. Исходим из того, что int? — значимый тип.

А может быть вы сомневаетесь, что int? — ссылочный тип? И вы уверены в этом, несмотря на выражение int? a = null? Что ж, давайте зайдём с другой стороны и будем отталкиваться от того, что int? — значимый тип.

В таком случае выражение int? a = null выглядит немного странно, но предположим, что это опять в C# сахара сверху насыпали. Получается, что a хранит какой-то объект. b тоже хранит какой-то объект. При инициализации переменных aObj и bObj будет произведена упаковка объектов, хранимых в a и b, в результате чего в aObj и в bObj будут записаны разные ссылки. Получается, что Object.ReferenceEquals в качестве аргументов принимает ссылки на разные объекты, следовательно…

Всё очевидно, ответ — False!

Всё очевидно, ответ — False!

Для тех, кто отталкивался от значимых типов, — если у вас вдруг закрались какие-то сомнения про сравнение ссылок, то можно посмотреть документацию по Object.ReferenceEquals на docs.microsoft.com. В частности, там тоже затрагивают тему значимых типов и упаковки/распаковки. Правда, там описывается кейс, когда экземпляры значимых типов передаются непосредственно в метод, мы же упаковку вынесли отдельно, но суть та же.

When comparing value types. If objA and objB are value types, they are boxed before they are passed to the ReferenceEquals method. This means that if both objA and objB represent the same instance of a value type, the ReferenceEquals method nevertheless returns false, as the following example shows.

Казалось бы, здесь статью можно и закончить, вот только… правильный ответ — True.

Что ж, давайте разбираться.

Разбираемся

Есть два пути — простой и интересный.

Простой путь

Интересный путь

На этой тропинке нам будет недостаточно документации. Она описывает поведение, но не отвечает на вопрос ‘почему’?

Что такое на самом деле int? и null в соответствующем контексте? Почему это работает так? В IL коде используются разные команды или нет? Отличается поведение на уровне CLR? Ещё какая-то магия?

Начнём с разбора сущности int?, чтобы вспомнить основы, и постепенно дойдём до разбора первоначального кейса. Так как C# — язык достаточно «приторный», периодически будем обращаться к IL коду, чтобы смотреть в суть вещей (да, документация по C# — не наш путь сегодня).

int?, Nullable

Здесь рассмотрим основы nullable value types в принципе (что из себя представляет, во что компилируются в IL и т.п.). Ответ на вопрос из задания рассмотрен в следующем разделе.

Рассмотрим фрагмент кода.

Несмотря на то, что на C# инициализация этих переменных выглядит по-разному, для всех них будет сгенерирован один и тот же IL код.

Как видно, в C# всё от души сдобрено синтаксическим сахаром, чтобы нам с вами жилось лучше, по факту же:

С инициализацией по умолчанию мы разобрались — соответствующий IL код мы видели выше. Что же происходит здесь, когда мы хотим проинициализировать aVal значением 62?

Взглянем на IL код:

Опять же, ничего сложного — на evaluation stack загружается адрес aVal, а также значение 62, после чего вызывается конструктор с сигнатурой Nullable (T). То есть два следующих выражения будут полностью идентичны:

В этом же можно убедиться, опять взглянув на IL код:

А что же касается проверок? Например, что на самом деле представляет из себя код следующего вида?

Правильно, для понимания вновь обратимся к соответствующему IL коду.

Для удобства работы тип Nullable определяет:

Упаковка Nullable

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

Результатом сравнения ссылок ожидаемо будет false, так как произошло 2 операции упаковки и создание двух объектов, ссылки на которые были записаны в obj1 и obj2.

Результат всё также ожидаем — false.

А теперь вместо 62 прописываем дефолтное значение.

Иии… результатом неожиданно становится true. Казалось бы, имеем всё те же 2 операции упаковки, создание двух объектов и ссылки на два разных объекта, но результат-то — true!

Ага, наверняка опять дело в сахаре, и что-то поменялось на уровне IL кода! Давайте посмотрим.

Как мы видим, везде упаковка происходит идентичным образом — значения локальных переменных загружается на evaluation stack (инструкция ldloc), после чего происходит сама упаковка за счёт вызова команды box, для которой указывается, какой, собственно, тип будем упаковывать.

Обращаемся к спецификации Common Language Infrastructure, смотрим описание команды box и находим интересное примечание касаемо nullable типов:

If typeTok is a value type, the box instruction converts val to its boxed form.… If it is a nullable type, this is done by inspecting val’s HasValue property; if it is false, a null reference is pushed onto the stack; otherwise, the result of boxing val’s Value property is pushed onto the stack.

Отсюда следует несколько выводов, расставляющих точки над ‘i’:

Состояние экземпляра перед упаковкой:

Состояние экземпляра перед упаковкой:

В исходном примере, который был в самом начале статьи, происходит точно то же самое:

Ради интереса заглянем в исходный код CoreCLR из упомянутого ранее репозитория dotnet/runtime. Нас интересует файл object.cpp, конкретно — метод Nullable::Box, который и содержит нужную нам логику:

Здесь всё то, о чём мы говорили выше. Если не храним значение — возвращаем NULL:

Иначе производим упаковку:

Заключение

Ради интереса предлагаю показать пример из начала статьи своим коллегам и друзьям. Смогут ли они дать верный ответ и обосновать его? Если нет, приглашайте их познакомиться со статьёй. Если же смогут — что ж, моё уважение!

Надеюсь, это было небольшое, но увлекательное приключение. 🙂

P.S. У кого-то мог возникнуть вопрос: а с чего вообще началось погружение в эту тему? Мы делали новое диагностическое правило в PVS-Studio на тему того, что Object.ReferenceEquals работает с аргументами, один из которых представлен значимым типом. Вдруг оказалось, что с Nullable есть неожиданный момент в поведении при упаковке. Посмотрели IL код — box как box. Посмотрели спецификацию CLI — ага, вот оно! Показалось, что это достаточно интересный кейс, про который стоит рассказать — раз! — и статья перед вами.

что такое nullable тип. Смотреть фото что такое nullable тип. Смотреть картинку что такое nullable тип. Картинка про что такое nullable тип. Фото что такое nullable тип

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Sergey Vasiliev. Check how you remember nullable value types. Let’s peek under the hood.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *