Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Ссылочные типы с допустимым значением NULL — это группа функций, которые стремятся свести к минимуму вероятность того, что ваш код вызывает ошибку System.NullReferenceExceptionво время выполнения. Три функции, которые помогают избежать этих исключений, включая возможность явно пометить ссылочный тип как допускающий значение null:
- Улучшен анализ статического потока, определяющий, может ли переменная быть
nullперед отменой ссылки. - Атрибуты, аннотирующие API, чтобы анализ потока определял состояние NULL.
- Аннотации переменных, которые разработчики используют для явного объявления предполагаемого состояния NULL для переменной.
Компилятор отслеживает состояние NULL каждого выражения в коде во время компиляции. NULL-состояние
-
not-null: выражение, как известно, не является-
null. -
может иметь значение NULL: выражение может быть
null.
Примечания переменных определяют нуллируемость переменной ссылочного типа:
-
не допускающий null: если вы присваиваете
nullзначение или возможно-null выражение переменной, компилятор выдает предупреждение. Переменные, которые не допускают значение NULL, по умолчанию имеют состояние null как not-null. -
Допустимость null: можно назначить
nullзначение или возможно-нулевое выражение для переменной. Если null-состояние переменной maybe-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 dereference of null
}
Вы узнаете:
- Анализ null-состояния компилятором: как компилятор определяет, является ли выражение не null или, возможно, null.
- Атрибуты , применяемые к API, которые предоставляют больше контекста для анализа состояния null компилятора.
- Аннотации переменных, допускающие NULL, которые предоставляют информацию о вашем намерении в отношении переменных. Заметки полезны для полей, параметров и возвращаемых значений, чтобы задать состояние NULL по умолчанию.
- Правила, управляющие аргументами универсального типа. Добавлены новые ограничения, так как параметры типа могут быть ссылочными типами или типами значений. Суффикс
?реализуется по-разному для типов значений, допускающих значение NULL, и ссылочных типов, допускающих значение NULL. - Контекст Nullable помогает мигрировать большие проекты. Вы можете включить предупреждения и заметки в контексте, допускающего значение NULL, в частях приложения при миграции. После устранения дополнительных предупреждений можно включить оба параметра для всего проекта.
Наконец, вы узнаете известные ловушки для анализа состояния NULL в struct типах и массивах.
Эти понятия также можно изучить в модуле Learn по безопасности null в C#.
Анализ нулевого состояния
анализ состояния NULL отслеживает состояния NULL ссылок. Выражение является not-null или maybe-null. Компилятор определяет, что переменная not-null, двумя способами:
- Переменная была назначена значением, которое, как известно, не null.
- Переменная была проверена на
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);
В предыдущем примере компилятор определяет, что message может быть нулевым при выводе первого сообщения. Предупреждение для второго сообщения отсутствует. Последняя строка кода выдает предупреждение, поскольку 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.
Анализ нулевого состояния не отслеживает вызываемые методы. В результате поля, инициализированные в общем вспомогательном методе, вызываемом всеми конструкторами, могут создать предупреждение со следующим сообщением:
Ненулевое свойство `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. В статье о разрешении предупреждений nullable приведены методы исправления предупреждений, которые вы, скорее всего, встретите в своем коде. Диагностика, произведенная из анализа нулевого состояния, является только предупреждениями.
Атрибуты в сигнатурах 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 на ненулевое внутри 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. Компилятор выдает предупреждение, когда код присваивает выражение maybe-null переменной, которая не должна иметь значение NULL.
-
Ссылка может иметь значение NULL. Состоянием по умолчанию для ссылочной переменной, допускающей значение NULL, является может быть NULL. Компилятор применяет правила, чтобы убедиться, что вы правильно проверяете ссылку
null:- Переменная может быть разыменована только в том случае, если компилятор может гарантировать, что значение не
null. - Эти переменные можно инициализировать со значением по умолчанию
nullи может быть назначено значениеnullв другом коде. - Компилятор не выдает предупреждений, когда коду присваивается выражение возможно-null переменной, которая может быть null.
- Переменная может быть разыменована только в том случае, если компилятор может гарантировать, что значение не
Любая ссылочная переменная, не допускающая null, имеет начальное состояние null как не-null . Любая nullable переменная-ссылка имеет начальное состояние 'может быть null'.
Ссылочный тип, допускающий значение NULL использует тот же синтаксис, что и тип значения, допускающего значение NULL: к типу переменной добавляется ?. Например, следующее объявление переменной представляет строковую переменную, допускающую значение NULL, name:
string? name;
Если включены Nullable-ссылочные типы, любая переменная, в которой 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. Предпочтительным вариантом для проверки параметра на null во время выполнения является ArgumentNullException.ThrowIfNull. Кроме того, поведение программы во время выполнения с использованием аннотаций 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.
Вы можете для небольших проектов включить ссылочные типы, допускающие значение 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 |
Отключено | Отключено | Все допускают значение NULL | Выдает предупреждение | Не оказывает влияния |
enable |
Включен | Включен | Не допускают значение NULL, если не объявлены с помощью ? |
Объявляет тип, допускающий значение NULL | Подавляет предупреждения о возможном назначении null |
warnings |
Включен | Не применимо | Все имеют значение NULL, но члены считаются не null при открытии фигурной скобки методов | Выдает предупреждение | Подавляет предупреждения о возможном назначении null |
annotations |
Отключено | Выключено | Не допускают значение 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: Восстанавливает флаг аннотации в настройках проекта.
Для любой строки кода можно задать любое из следующих сочетаний:
| Флаг предупреждения | Флаг аннотации | Использование |
|---|---|---|
| проект по умолчанию | проект по умолчанию | По умолчанию. |
| включить | отключить | Исправление предупреждений анализа |
| включить | проект по умолчанию | Исправить предупреждения при анализе |
| проект по умолчанию | включить | Добавьте аннотации типа |
| включить | включить | Код уже перенесен |
| отключить | включить | Добавление аннотации к коду перед исправлением предупреждений |
| отключить | отключить | Добавление устаревшего кода в перенесенный проект |
| проект по умолчанию | отключить | Редко |
| отключить | проект по умолчанию | Редко |
Эти девять сочетаний позволяют вам тщательно контролировать диагностику, которую компилятор выдает для вашего кода. Вы можете включить дополнительные функции в любой области, которую вы обновляете, без просмотра дополнительных предупреждений, которые вы еще не готовы к устранению.
Внимание
Глобальный контекст, допускающий значения NULL, не применяется для созданных файлов кода. В любом случае контекст, допускающий значение NULL, отключен для любого исходного файла, помеченного как созданный. Это означает, что любые интерфейсы API в создаваемых файлах не аннотируются. Предупреждения, допускающие значение NULL, не создаются для созданных файлов. Существует четыре способа пометки файла как созданного:
- В файле. editorconfig укажите
generated_code = trueв разделе, который применяется к этому файлу. - Вставьте
<auto-generated>или<auto-generated/>в комментарий в верхней части файла. Он может находиться в любой строке комментария, однако блок комментариев должен быть первым элементом в файле. - Имя файла следует начинать с TemporaryGeneratedFile_
- В конце имени файла следует указать .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 во время выполнения. Она присваивается строке типа "non-nullable" без каких-либо предупреждений.
Массивы
При использовании ссылочных типов, допускающих значения 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 разыменовывается, в результате чего во время выполнения возникает исключение.
Конструкторы
Конструктор класса по-прежнему вызывает финализатор, даже если в этом конструкторе возникло исключение.
В следующем примере демонстрируется это поведение:
public class A
{
private string _name;
private B _b;
public A(string name)
{
ArgumentNullException.ThrowIfNullOrEmpty(name);
_name = name;
_b = new B();
}
~A()
{
Dispose();
}
public void Dispose()
{
_b.Dispose();
GC.SuppressFinalize(this);
}
}
public class B: IDisposable
{
public void Dispose() { }
}
public void Main()
{
var a = new A(string.Empty);
}
В предыдущем примере System.NullReferenceException будет вызвано, когда _b.Dispose(); выполняется, если параметр name был null. Вызов, который _b.Dispose(); никогда не будет вызываться, когда конструктор завершается успешно. Однако нет предупреждения, выданного компилятором, так как статический анализ не может определить, завершается ли метод (например, конструктор) без исключения среды выполнения.