Поделиться через


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

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

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

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

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

Примечание.

Вы можете назначить Nullable параметр для проекта с помощью тега <Nullable> . Дополнительные сведения см. в параметрах компилятора.

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

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

Включение поддержки nullable по умолчанию требует проведения дополнительной работы для добавления директив препроцессора в каждый файл. Преимущество заключается в том, что для каждого нового файла кода, добавляемого в проект, будет включена поддержка null. Любая новая работа будет поддерживать значения null; необходимо будет обновить только существующий код. Отключение этого параметра как параметра по умолчанию работает лучше, если библиотека является стабильной и основной задачей разработки является внедрение ссылочных типов, допускающих значение NULL. Вы включаете ссылочные типы, допускающие значение null, по мере аннотирования API. По завершении работы ссылочные типы, допускающие значение null, включаются для всего проекта. При создании нового файла необходимо добавить директивы препроцессора и обеспечить поддержку nullable. Если кто-то из разработчиков вашей команды забудет об этом, новый код теперь станет частью очереди задач на внедрение поддержки 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?, является nullable, но при отключении контекста аннотации может появиться соответствующее предупреждение. Переменные, объявленные с помощью var, являются nullable, если включен контекст аннотации.

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

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

Каждая переменная имеет состояние по умолчанию, определяющее ее возможность быть NULL, которое зависит от её допустимости к null-значению.

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

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

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

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

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

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

Включите аннотации типов

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

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

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

Следующие шаги

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

Эти понятия также можно изучить в модуле Learn по безопасности null в C#.