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


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

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

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

Компилятор отслеживает состояние NULL каждого выражения в коде во время компиляции. NULL-состояние имеет одно из двух значений:

  • not-null: выражение, как известно, не является-null.
  • может иметь значение NULL: выражение может быть null.

Примечания переменных определяют допустимость значений NULL переменной ссылочного типа:

  • непустимый: если вы назначаете null значение или выражение, возможно, null переменной, компилятор выдает предупреждение. Переменные, не допускающие значение NULL, имеют значение null-state of not-NULL.
  • Значение NULL: можно назначить null значение или выражение, возможно, null переменной. Если значение null-состояния переменной может иметь значение NULL, компилятор выдает предупреждение, если вы разоменовываете переменную. Состояние NULL по умолчанию для переменной может иметь значение NULL.

В остальной части этой статьи описывается, как эти три области функций работают для создания предупреждений, когда код может разыменовки значение null. Разыменование переменной означает доступ к одному из ее членов с помощью оператора . (точка), как показано в следующем примере:

string message = "Hello, World!";
int length = message.Length; // dereferencing "message"

При разыменовании переменной, значение которой равно null, среда выполнения создает исключение System.NullReferenceException.

Аналогичным образом можно создавать предупреждения, если [] нотация используется для доступа к члену объекта, если объект имеет nullследующий тип:

using System;

public class Collection<T>
{
    private T[] array = new T[100];
    public T this[int index]
    {
        get => array[index];
        set => array[index] = value;
    }
}

public static void Main()
{
    Collection<int> c = default;
    c[10] = 1;    // CS8602: Possible derefence of null
}

Вы узнаете:

  • Анализ состояния null компилятора: как компилятор определяет, является ли выражение не null или, возможно, null.
  • Атрибуты , применяемые к API, которые предоставляют больше контекста для анализа состояния null компилятора.
  • Заметки переменных, допускающие значение NULL, которые предоставляют сведения о намерении переменных. Заметки полезны для полей, параметров и возвращаемых значений, чтобы задать состояние NULL по умолчанию.
  • Правила, управляющие аргументами универсального типа. Добавлены новые ограничения, так как параметры типа могут быть ссылочными типами или типами значений. Суффикс ? реализуется по-разному для типов значений, допускающих значение NULL, и ссылочных типов, допускающих значение NULL.
  • Контекст Nullable помогает мигрировать большие проекты. Вы можете включить предупреждения и заметки в контексте, допускающего значение NULL, в частях приложения при миграции. После устранения дополнительных предупреждений можно включить оба параметра для всего проекта.

Наконец, вы узнаете известные ловушки для анализа состояния NULL в struct типах и массивах.

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

Анализ нулевого состояния

анализ состояния NULL отслеживает состояния NULL ссылок. Выражение имеет значение not-null или , возможно, null. Компилятор определяет, что переменная not-null, двумя способами:

  1. Переменная была назначена значением, которое, как известно, не null.
  2. Переменная была проверена на null и не была присвоена с тех пор.

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

  • Если переменная не-null, переменную можно безопасно разыменовать.
  • Если это переменная maybe-null, ее необходимо проверить, чтобы убедиться, что она не равна null, перед разыменованием.

Рассмотрим следующий пример:

string? message = null;

// warning: dereference null.
Console.WriteLine($"The length of the message is {message.Length}");

var originalMessage = message;
message = "Hello, World!";

// No warning. Analysis determined "message" is not-null.
Console.WriteLine($"The length of the message is {message.Length}");

// warning!
Console.WriteLine(originalMessage.Length);

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

void FindRoot(Node node, Action<Node> processNode)
{
    for (var current = node; current != null; current = current.Parent)
    {
        processNode(current);
    }
}

Предыдущий код не создает предупреждений для разыменования переменной current. Статический анализ определяет, что переменная current никогда не будет разыменована, если она maybe-null. Переменная current проверяется на null до доступа к current.Parent и перед передачей current в действие ProcessNode. В предыдущих примерах показано, как компилятор определяет состояние NULL для локальных переменных при инициализации, назначении или сравнении с null.

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

Ненулевое свойство name должно содержать ненулевое значение при выходе конструктора.

Эти предупреждения можно устранить одним из двух способов: цепочка конструкторов или атрибуты , допускающие значение NULL, в вспомогательном методе. В приведенном ниже коде показан пример каждого метода. Класс Person использует общий конструктор, вызываемый всеми другими конструкторами. Класс Student содержит вспомогательный метод, аннотированный атрибутом System.Diagnostics.CodeAnalysis.MemberNotNullAttribute :


using System.Diagnostics.CodeAnalysis;

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person(string firstName, string lastName)
    {
        FirstName = firstName;
        LastName = lastName;
    }

    public Person() : this("John", "Doe") { }
}

public class Student : Person
{
    public string Major { get; set; }

    public Student(string firstName, string lastName, string major)
        : base(firstName, lastName)
    {
        SetMajor(major);
    }

    public Student(string firstName, string lastName) :
        base(firstName, lastName)
    {
        SetMajor();
    }

    public Student()
    {
        SetMajor();
    }

    [MemberNotNull(nameof(Major))]
    private void SetMajor(string? major = default)
    {
        Major = major ?? "Undeclared";
    }
}

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

Атрибуты в сигнатурах API

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

void PrintMessageUpper(string? message)
{
    if (!IsNull(message))
    {
        Console.WriteLine($"{DateTime.Now}: {message.ToUpper()}");
    }
}

bool IsNull(string? s) => s == null;

Основываясь на проверке, любой разработчик будет считать этот код безопасным и не должен создавать предупреждения. Однако компилятор не знает, что IsNull предоставляет проверку null и выдает предупреждение для message.ToUpper() инструкции, учитывая message , что это может быть переменная null . NotNullWhen Используйте атрибут для исправления этого предупреждения:

bool IsNull([NotNullWhen(false)] string? s) => s == null;

Этот атрибут сообщает компилятору, что, если IsNull возвращается false, параметр s не имеет значения NULL. Компилятор изменяет состояниеmessage NULL на непустую if (!IsNull(message)) {...} внутри блока. Предупреждения не выдаются.

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

Аннотации для переменных, допускающих значения NULL

Анализ состояния NULL обеспечивает надежный анализ локальных переменных. Компилятору требуются дополнительные сведения о переменных элементов. Компилятору требуется дополнительная информация, чтобы задать состояние NULL всех полей в открывающей скобке элемента. Любой из доступных конструкторов можно использовать для инициализации объекта. Если для поля элемента можно задать значение null, в начале каждого метода компилятор должен предположить, что его состояние NULL — maybe-null.

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

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

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

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

string? name;

Если включены ссылочные типы, допускающие значение NULL, любая переменная, в которой ? не добавляется имя типа, является ненулевой ссылочным типом. Это включает все переменные ссылочного типа в существующем коде после включения этой функции. Однако любые неявные типизированные локальные переменные (объявленные с помощью var) являются ссылочными типами, допускающими значение NULL. Как показано в предыдущих разделах, статический анализ определяет состояние NULL локальных переменных, чтобы определить, может ли они иметь значение NULL перед разыменовыванием.

Иногда необходимо переопределить предупреждение, если известно, что переменная не имеет значение NULL, но компилятор определяет ее состояние NULL как maybe-null. Используйте оператор, допускающий значение NULL! перед именем переменной, чтобы принудительно задать для состояния NULLnot-null. Например, если вы знаете, что переменная name не имеет значение null, а компилятор выдает предупреждение, напишите следующий код, чтобы переопределить анализ компилятора:

name!.Length;

Ссылочные типы, допускающие значение NULL, и типы значений, допускающие значение NULL, предоставляют аналогичную семантическую концепцию: переменная может представлять собой значение или объект или может быть null. Однако ссылочные типы, допускающие значение NULL, и типы значений, допускающие значение NULL, реализуются по-разному: типы значений, допускающие значение NULL, реализуются с помощью System.Nullable<T>, а ссылочные типы, допускающие значение NULL, реализуются атрибутами, которые считывает компилятор. Например, string? и string представлены одним и тем же типом: System.String. Однако int? и int представлены System.Nullable<System.Int32> и System.Int32 соответственно.

Ссылочные типы, допускающие значение NULL, являются функцией времени компиляции. Это означает, что вызывающие пользователи могут игнорировать предупреждения, намеренно использовать null в качестве аргумента для метода, ожидающего ненулевой ссылки. Авторы библиотеки должны включать проверки во время выполнения в отношении значений аргументов NULL. Это ArgumentNullException.ThrowIfNull предпочтительный вариант проверки параметра на значение NULL во время выполнения. Кроме того, поведение программы во время выполнения с использованием аннотаций nullable остаётся тем же, если все аннотации nullable (? и !) удалены. Их единственная цель — выражение намерения проектирования и предоставление сведений для анализа состояния NULL.

Внимание

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

Универсальные шаблоны

Для универсальных шаблонов требуются подробные правила для обработки T? для любых типов параметров T. Правила должны быть подробными, учитывая журнал и другую реализацию типа значения, допускающего значение NULL, и ссылочного типа, допускающего значение NULL. Типы значений, допускающие значение NULL, реализуются с помощью структуры System.Nullable<T>. Ссылочные типы, допускающие значения NULL, реализуются как аннотации типа, предоставляющие компилятору семантические правила.

  • Если аргумент типа для T является ссылочным типом, T? ссылается на соответствующий тип ссылки, допускающий значение NULL. Например, если T равен string, то T? — string?.
  • Если аргумент типа для T является типом значения, T? ссылается на тот же тип значения, T. Например, если T имеет значение int, то T? также является int.
  • Если аргумент типа для T является ссылочным типом, допускающим значение NULL, T? ссылается на тот же ссылочный тип, допускающий значение NULL. Например, если T равен string?, то T? — string?.
  • Если аргумент типа для T является типом значения, допускающим значение NULL, T? ссылается на тот же тип значения, допускающий значение NULL. Например, если T равен int?, то T? — int?.

Для возвращаемых значений T? эквивалентно [MaybeNull]T; для значений аргументов аргумент T? эквивалентен [AllowNull]T. Дополнительные сведения см. в статье об атрибутах для анализа состояния NULL в справочнике по языку.

Можно указать другое поведение с помощью ограничений.

  • Ограничение class означает, что T должен быть ссылочным типом, не допускающим значения NULL (например, string). Компилятор выдает предупреждение при использовании ссылочного типа, допускающего значение NULL, например string? для T.
  • Это ограничение class? означает, что T должен быть ссылочным типом, не допускающим значение NULL (string), или ссылочным типом, допускающим значение NULL (например, string?). Если параметр типа является ссылочным типом, допускающим значение NULL, например string?, выражение T? ссылается на тот же ссылочный тип, допускающий значение NULL, например string?.
  • Ограничение notnull означает, что T должен быть ссылочным типом, не допускающим значения NULL, или типом значения, не допускающим значения NULL. Если для параметра типа используется ссылочный тип, допускающий значение NULL, или тип значения, допускающий значение NULL, компилятор выдает предупреждение. Кроме того, если T является типом значения, возвращаемое значение относится к этому типу значения, а не к соответствующему типу значения, допускающему значение NULL.

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

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

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

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

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

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

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

  • оба отключены: код без учета NULL. Отключите поведение до включения ссылочных типов, допускающих значение NULL, за исключением того, что новый синтаксис выдает предупреждения вместо ошибок.
    • Предупреждения о значении NULL отключены.
    • Все переменные ссылочного типа являются ссылочными типами, допускающими значение 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 Выключено Выключено Все допускают значение NULL Выдает предупреждение Не оказывает влияния
enable Включен Включен Не допускают значение NULL, если не объявлены с помощью ? Объявляет тип, допускающий значение NULL Подавляет предупреждения о возможном назначении null
warnings Включен Нет данных Все имеют значение NULL, но члены считаются не null при открытии фигурной скобки методов Выдает предупреждение Подавляет предупреждения о возможном назначении null
annotations Выключено Выключено Не допускают значение NULL, если не объявлены с помощью ? Объявляет тип, допускающий значение NULL Не оказывает влияния

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

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

Известные ошибки

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

Структуры

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

using System;

#nullable enable

public struct Student
{
    public string FirstName;
    public string? MiddleName;
    public string LastName;
}

public static class Program
{
    public static void PrintStudent(Student student)
    {
        Console.WriteLine($"First name: {student.FirstName.ToUpper()}");
        Console.WriteLine($"Middle name: {student.MiddleName?.ToUpper()}");
        Console.WriteLine($"Last name: {student.LastName.ToUpper()}");
    }

    public static void Main() => PrintStudent(default);
}

В предыдущем примере в PrintStudent(default) не возвращается предупреждение, хотя ссылочные типы FirstName и LastName, не допускающие значения NULL, имеют значение NULL.

Еще один более распространенный случай связан с работой с универсальными структурами. Рассмотрим следующий пример:

#nullable enable

public struct S<T>
{
    public T Prop { get; set; }
}

public static class Program
{
    public static void Main()
    {
        string s = default(S<string>).Prop;
    }
}

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

Массивы

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

using System;

#nullable enable

public static class Program
{
    public static void Main()
    {
        string[] values = new string[10];
        string s = values[0];
        Console.WriteLine(s.ToUpper());
    }
}

В предыдущем примере объявление массива показывает, что он содержит строки, не допускающие значения NULL, а все элементы инициализируются с использованием значения null. После этого переменной s присваивается значение null (первый элемент массива). Наконец, переменная s разыменовывается, в результате чего во время выполнения возникает исключение.

См. также