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

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

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

Планирование миграции

Независимо от способа обновления базы кода, целью является включение в проект предупреждений и аннотаций, допускающих значения NULL. После достижения этой цели у вас в проекте будет параметр <nullable>Enable</nullable>. Для настройки параметров в других местах директивы pragma не требуются.

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

  1. Отключение параметра Nullable в качестве значения по умолчанию _: _disable является параметром по умолчанию, если в файл проекта не добавляется элемент Nullable. Используйте это значение по умолчанию, если вы не добавляете новые файлы в базу кода. Основным действием является обновление библиотеки для использования ссылочных типов, допускающих значение NULL. Выбор этого значения по умолчанию означает, что при обновлении кода в каждый файл добавляется директива pragma, допускающая значение NULL.
  2. Разрешение значений NULL в качестве значений по умолчанию: установите это значение по умолчанию, если вы активно разрабатываете новые функции. Требуется, чтобы новый код использовал ссылочные типы, допускающие значение NULL, и статический анализ, допускающий значение NULL. Использование этого значения по умолчанию означает, что необходимо добавить #pragma nullable disable в верхнюю часть каждого файла. Вы удалите эту директиву pragma, как только приступите к устранению предупреждений в этом файле.
  3. Предупреждения, допускающие значения NULL, в качестве значения по умолчанию _: выберите это значение по умолчанию для двухэтапной миграции. На первом этапе необходимо устранить предупреждения. На втором этапе включите аннотации для объявления ожидаемой переменной _null-state. Использование этого значения по умолчанию означает, что необходимо добавить #pragma nullable disable в верхнюю часть каждого файла.
  4. Аннотации, допускающие значение NULL, в качестве значений по умолчанию. Добавьте аннотацию к коду перед тем, как устранить предупреждения.

Выбор параметра "Допускаются значения NULL" в качестве параметра по умолчанию добавляет работы на начальном этапе, поскольку нужно включить директиву pragma в каждый файл. Преимущество заключается в том, что для каждого нового файла кода, добавленного в проект, будет разрешено значение null. Любая новая работа будет поддерживать значения null; необходимо будет обновить только существующий код. Отключение этого параметра как параметра по умолчанию работает лучше, если библиотека является стабильной и основной задачей разработки является внедрение ссылочных типов, допускающих значение NULL. Ссылочные типы, допускающие значение null, включаются по мере аннотирования API. По завершении работы ссылочные типы, допускающие значение null, включаются для всего проекта. При создании нового файла необходимо добавить директивы pragma и настроить в нем разрешения для значений NULL. Если кто-то из разработчиков вашей команды забудет это сделать, новый код перейдет в разряд невыполненной работы по включению во всем коде поддержки значений null.

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

Важно!

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

  1. В файле. editorconfig укажите generated_code = true в разделе, который применяется к этому файлу.
  2. Вставьте <auto-generated> или <auto-generated/> в комментарий в верхней части файла. Он может находиться в любой строке комментария, однако блок комментариев должен быть первым элементом в файле.
  3. Имя файла следует начинать с TemporaryGeneratedFile_
  4. В конце имени файла следует указать .designer.cs, .generated.cs, .g.cs или .g.i.cs.

Генераторы могут явно использовать директиву препроцессора #nullable.

Общие сведения о контекстах и предупреждениях

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

  • oblivious: все ссылочные типы имеют свойство oblivious и допускают значение NULL, если контекст аннотации отключен.
  • nonnullable: ссылочный тип без аннотации C не допускает значение NULL, если включен контекст аннотации.
  • nullable: ссылочный тип с аннотацией C? допускает значение NULL, но при отключении контекста аннотации может выводиться предупреждение. Переменные, объявленные с помощью var, допускают значение NULL, если включен контекст аннотации.

Компилятор создает предупреждения с учетом допустимости значений NULL.

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

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

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

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

Устранение предупреждений

Если в проекте используется Entity Framework Core, следует ознакомиться с рекомендациями по работе со ссылочными типами, допускающими значение NULL.

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

Перед тем как продолжить работу с другим кодом, можно устранить предупреждения и включить аннотации в каждом файле или классе. Однако зачастую более эффективным способом было бы устранить предупреждения, созданные во время использования контекста предупреждений перед тем, как включить аннотации типа. Таким образом, все типы будут иметь свойство oblivious до тех пор, пока не будет устранен первый набор предупреждений.

Включение аннотаций для типа

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

Атрибуты расширяют аннотации типов

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

Дальнейшие действия

После включения аннотаций и устранения всех предупреждений можно задать контекст по умолчанию enabled для проекта. Если в код были добавлены директивы pragma для контекста аннотации или предупреждения, допускающего значение NULL, их можно удалить. Со временем могут появиться новые предупреждения. Вы можете написать код, который представляет предупреждения. Можно обновить зависимость библиотеки для ссылочных типов, допускающих значение NULL. Эти обновления изменят типы в этой библиотеке со свойством nullable oblivious на nonnullable или nullable.