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

Примечание.

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

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

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

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

Подсказка

Чтобы узнать, когда функция впервые появилась в C#, ознакомьтесь со статьей по журналу версий языка C#.

Внимание

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

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

  • Необходимо инициализировать переменную ссылочного типа T с ненулевым значением, и вы никогда не можете назначить значение, которое может быть null.
  • Вы можете инициализировать переменную ссылочного типа T? с null помощью или назначить nullее, но перед отменой ссылок необходимо проверить ее.null
  • При применении оператора null-forgiving к переменной типаm, как и вT?, переменная m! считается ненулевой.

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

string notNull = "Hello";
string? nullable = default;
notNull = nullable!; // null forgiveness

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

public MyClass : System.Object? // not allowed
{
}

var nullEmpty = System.String?.Empty; // Not allowed
var maybeObject = new object?(); // Not allowed
try
{
    if (thing is string? nullableString) // not allowed
        Console.WriteLine(nullableString);
} catch (Exception? e) // Not Allowed
{
    Console.WriteLine("error");
}

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

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

Внимание

Заметки, допускающие значение NULL, не вводят изменения в поведении, но другие библиотеки могут использовать отражение для создания другого поведения среды выполнения для ссылочных типов, допускающих значение NULL и не допускающих значение NULL. В частности, Entity Framework Core считывает атрибуты, допускающие значение NULL. Он интерпретирует nullable ссылку как необязательное значение, а ненулевую ссылку как обязательное значение.

В контексте, допускающем значение 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, присвоив ему ненулевое значение ссылочной переменной, создается предупреждение компилятора. В следующем классе показаны примеры этих предупреждений:

public class ProductDescription
{
    private string shortDescription;
    private string? detailedDescription;

    public ProductDescription() // Warning! shortDescription not initialized.
    {
    }

    public ProductDescription(string productDescription) =>
        this.shortDescription = productDescription;

    public void SetDescriptions(string productDescription, string? details=null)
    {
        shortDescription = productDescription;
        detailedDescription = details;
    }

    public string GetDescription()
    {
        if (detailedDescription.Length == 0) // Warning! dereference possible null
        {
            return shortDescription;
        }
        else
        {
            return $"{shortDescription}\n{detailedDescription}";
        }
    }

    public string FullDescription()
    {
        if (detailedDescription == null)
        {
            return shortDescription;
        }
        else if (detailedDescription.Length > 0) // OK, detailedDescription can't be null.
        {
            return $"{shortDescription}\n{detailedDescription}";
        }
        return shortDescription;
    }
}

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

string shortDescription = default; // Warning! non-nullable set to null;
var product = new ProductDescription(shortDescription); // Warning! static analysis knows shortDescription maybe null.

string description = "widget";
var item = new ProductDescription(description);

item.SetDescriptions(description, "These widgets will do everything.");

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

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

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

заметки и предупреждения параметры по умолчанию отключены для существующих проектов. Начиная с .NET 6 (C# 10), оба флага по умолчанию включены для проектов new. Причина двух разных флагов для контекста, допускающего значение NULL, заключается в том, чтобы упростить перенос больших проектов, которые существовали до введения ссылочных типов, допускающих значение NULL.

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

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

Контекст аннотаций, допускающий значение NULL, определяет поведение компилятора. Существует четыре варианта настроек контекста \"nullable\" и:

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

Контекст заметки, допускающий значение NULL, и контекст предупреждения, допускающий значение NULL для проекта, можно задать с помощью <Nullable> элемента в CSPROJ-файле . Этот элемент настраивает, как компилятор интерпретирует значение NULL типов и какие предупреждения он выдает. В следующей таблице показаны допустимые значения и приводится сводка по задаваемым контекстам.

Контекст Предупреждения о разыменовании Предупреждения о назначении Типы ссылок Суффикс ? Оператор !
disable Disabled Disabled Все допускают значение NULL Выдает предупреждение Не оказывает влияния
enable Enabled Enabled Не допускают значение NULL, если не объявлены с помощью ? Объявляет тип, допускающий значение NULL Подавляет предупреждения о возможном назначении null
warnings Enabled Неприменимо Все имеют значение NULL, но члены считаются не null при открытии фигурной скобки методов Выдает предупреждение Подавляет предупреждения о возможном назначении null
annotations Disabled Disabled Не допускают значение NULL, если не объявлены с помощью ? Объявляет тип, допускающий значение NULL Не оказывает влияния

Переменные ссылочного типа в коде, скомпилированном в отключенном контексте, являются пустыми. Вы можете назначить null литерал или переменную с значением NULL переменной, которая является ненуловимой. Однако по умолчанию состояние переменной nullable oblivious равно not-null.

Выберите параметр, который лучше всего подходит для проекта:

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

Пример:

<Nullable>enable</Nullable>

Вы также можете использовать директивы для задания этих же флагов в любом месте исходного кода. Эти директивы наиболее полезны при переносе большой базы кода.

  • #nullable enable. Задает флаги заметки и предупреждения для включить.
  • #nullable disable. Задает флаги заметки и предупреждения, чтобы отключить.
  • #nullable restore: восстанавливает флаг заметки и флаг предупреждения в параметрах проекта.
  • #nullable disable warnings: задает флаг предупреждения для отключения.
  • #nullable enable warnings: задает флаг предупреждения для включения.
  • #nullable restore warnings: восстанавливает флаг предупреждения в параметрах проекта.
  • #nullable disable annotations: задает флаг заметки для отключения.
  • #nullable enable annotations: задает флаг заметки для включения.
  • #nullable restore annotations: Восстанавливает флаг аннотации в настройках проекта.

Для любой строки кода можно задать любое из следующих сочетаний:

Флаг предупреждения Флаг аннотации Использование
проект по умолчанию проект по умолчанию По умолчанию
включить disable Исправление предупреждений анализа
включить проект по умолчанию Исправление предупреждений анализа
проект по умолчанию включить Добавьте аннотации типа
включить включить Код уже перенесен
disable включить Добавление аннотации к коду перед исправлением предупреждений
disable disable Добавление устаревшего кода в перенесенный проект
проект по умолчанию disable Редко
disable проект по умолчанию Редко

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

Внимание

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

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

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

По умолчанию флаги заметки и предупреждения, допускающие значение NULL, отключены. Это означает, что существующий код компилируется без изменений и без создания новых предупреждений. Начиная с .NET 6, новые проекты включают элемент <Nullable>enable</Nullable> во всех шаблонах проектов, установив эти флаги на enabled.

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

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

Контекст, допускающий значение NULL, можно управлять двумя способами. На уровне проекта добавьте <Nullable>enable</Nullable> параметр проекта. В одном исходном файле C# добавьте #nullable enable pragma, чтобы включить контекст, допускающий значение NULL. Дополнительные сведения см. в разделе о настройке стратегии, допускаемой на значение NULL. До .NET 6 новые проекты используют значение по умолчанию, <Nullable>disable</Nullable>. Начиная с .NET 6 все файлы новых проектов содержат элемент <Nullable>enable</Nullable>.

Обобщенные понятия

При использовании параметра типа , Tв качестве его допустимого значения NULL, T?фактический аргумент типа определяет, как интерпретируется.? Рассмотрим следующее универсальное объявление:

public class Box<T>
{
    public T Contents { get; set; }
}

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

  • Аргумент типа — это ненулевой ссылочный тип. Для Box<string>, T имеет и T? имеет string?string значение — соответствующий ссылочный тип, допускающий значение NULL.
  • Аргумент типа — это тип значения. Для Box<int>, T есть и T? есть int также — один и тот же intтип значения. Заметка не влияет на типы значений, если параметр типа не имеет struct ограничения, в этом случае T? означает Nullable<T> (int?).
  • Аргумент типа уже имеет значение NULL. Для Box<string?>, T есть string? и T? по-прежнему string?. Вы не получаете тип "вдвойне null".

Ограничения ограничивают допустимые аргументы типа. Они также позволяют компилятору подумать о том, как T можно использовать:

  • where T : class требуется ненулевой ссылочный тип. Box<string> разрешено; Box<string?> создает предупреждение.
  • where T : class? позволяет использовать ссылочный тип, допускающий значение NULL или не допускающий значения NULL. Оба Box<string> и Box<string?> разрешены.
  • where T : struct требуется тип значения, не допускающего значение NULL. Box<int> разрешено; Box<int?> Не. С этим ограничением внутри T? универсальных средств Nullable<T>— для Box<int>, T? является int?.
  • where T : notnull требуется непустая ссылка или тип значения. Box<string> и Box<int> разрешено; Box<string?> создается предупреждение.
  • where T : BaseType требует ненулевого ссылочного типа, наследуемого от BaseType. Добавление ? (where T : BaseType?) для разрешения производных типов, допускающих значение NULL, а также.

Ограничения помогают компилятору по поводу использования параметра универсального типа:

public static T? FirstOrDefault<T>(IEnumerable<T> source)
{
    foreach (T item in source)
    {
        return item;
    }
    return default;
}

public static void RequireNotNull<T>(T value) where T : notnull
{
    ArgumentNullException.ThrowIfNull(value);
}

public static void Generics()
{
    string? first = FirstOrDefault<string>([]);
    Console.WriteLine(first ?? "<empty>");

    RequireNotNull("not null");
}

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

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

См. также