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

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

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

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

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

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

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

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

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

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

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

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

Вы узнаете:

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

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

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

Анализ состояния NULL

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

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

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

Любая переменная, которую компилятор не определил как not-null, считается maybe-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";
    }
}

Примечание.

В C# 10 были добавлены ряд улучшений для определенного назначения и анализа состояния NULL. При обновлении до C# 10 сократится число ложноположительных результатов с предупреждением о NULL. Дополнительные сведения об улучшениях см. в статье о спецификации функций для определенного присваивания.

Анализ состояния, допускающий значение 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, name:

string? name;

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

Иногда необходимо переопределить предупреждение, если известно, что переменная не имеет значение NULL, но компилятор определяет ее состояние NULL как maybe-null. Оператор null-forgiving используется после имени переменной, чтобы принудительно заставить состояние NULL не иметь значение 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 во время выполнения.

Внимание

Включение заметок, допускающих значение 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

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

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

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

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

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

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

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

Внимание

Глобальный контекст, допускающий значения 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 отключены. Это означает, что существующий код компилируется без изменений и без создания новых предупреждений. Начиная с .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 разыменовывается, в результате чего во время выполнения возникает исключение.

См. также