Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Tip
Впервые сталкиваетесь с типами-ссылками, допускающими значение NULL? Сначала ознакомьтесь с ссылочными типами, допускающими значение NULL, чтобы понять аннотации и анализ значений NULL. В этой статье предполагается, что вы видите предупреждения в проекте, где включена эта функция.
Ищете определенный код ошибки компилятора? Справочная статья Устранение предупреждений о допускающих значение NULL типах перечисляет все предупреждения CS86xx и соответствующие способы их устранения.
При включении ссылочных типов, допускающих значение NULL, компилятор выдает предупреждения везде, где поведение кода не соответствует его заметкам. Большинство предупреждений попадают в небольшой набор шаблонов. После распознавания шаблона исправление обычно является одним из пяти методов:
- Добавьте флажок NULL.
- Добавьте или удалите аннотацию
?или!. - Добавьте атрибут, описывающий пустой контракт.
- Правильно инициализируйте переменные.
- Проверьте параметр проекта.
В этой статье рассматриваются все методики с репрезентативным примером. Цель не в том, чтобы подавлять предупреждения. Это делает намерение обработки null кода явным, чтобы компилятор достиг те же выводы, что и у вас.
Состояние NULL: то, что компилятор отслеживает
Прежде чем перейти к этим приёмам, полезно понять, как компилятор отслеживает потенциальные нарушения null-состояния. Когда он считывает код, компилятор отслеживает состояние null каждого выражения: его анализ о том, может ли выражение находиться null в этом месте в коде. Состояние NULL является одним из двух значений:
-
not-null — компилятор может доказать, что выражение не
nullздесь. Его можно безопасно использовать без проверки. -
может быть null — компилятор не может исключить
null. Использование выражения без проверки выдает предупреждение.
Переменная в состоянии NULL изменяется, так как компилятор следует коду. Метод, который может вернуть null, возвращает результат maybe-null. Проверка if (x is not null) сужает x до not-null в блоке if. Предупреждения, которые вы видите, означают, что компилятор сообщает вам: он определил, что выражение находится в состоянии maybe-null, а вы собираетесь использовать его так, как будто оно находится в состоянии not-null. Каждый метод в остальной части этой статьи отличается от способа предоставления компилятору сведений, необходимых для обеспечения того, чтобы выражение не равно NULL перед его использованием.
Добавить проверку на null
Наиболее распространённое предупреждение — возможное разыменование значения null. Компилятор отследил null-состояние переменной до возможно, null и обнаружил, что переменная использовалась без проверки:
public static int LengthOfMessageUnsafe(string? message)
{
// Warning CS8602: dereference of a possibly null reference.
return message.Length;
}
Обычно исправление — это защитное условие. Защитная проверка — это проверка в начале метода или блока, которая выполняет return или генерирует исключение, если входные данные недопустимы. Продолжается только безопасный путь. После выполнения проверки компилятор обновляет состояние NULL переменной на ненул в безопасном пути:
public static int DereferenceFixed(string? message)
{
if (message is null)
{
return 0;
}
// No warning: the compiler knows message is not-null on this path.
return message.Length;
}
Сопоставление шаблонов (например, выражения, такие как is null или is { } которые проверяют форму значения), ??и ??= включают в себя значения NULL:
public static int NullOperatorsFix(string? message)
{
// ?. evaluates to null if message is null; ?? supplies the fallback value.
int length = message?.Length ?? 0;
// Pattern matching narrows the type on the matching branch.
if (message is { Length: > 0 })
{
length = message.Length;
}
return length;
}
Шаблон { Length: > 0 } свойств совпадает только в том случае, если message свойство не равно null , а его Length свойство больше нуля, поэтому компилятор обрабатывает message как ненулевоеif значение внутри блока. Более простой is not null тест создает то же сужение состояния NULL, не проверяя какие-либо свойства.
Подробный обзор операторов см. в разделе "Операторы NULL".
Настройка заметок
Компилятор также предупреждает вас, когда в вашем коде выражение maybe-null присваивается переменной, не допускающей значение NULL. Это предупреждение означает одно из двух вещей:
- Переменная должна разрешать значения NULL. В этом случае добавьте
?к типу. - Выражение никогда не создает значение NULL. Аннотируйте API, который его создал.
public static void AssignmentWarning()
{
// Warning CS8600: converting null literal or possible null value to non-nullable type.
string name = Lookup("nobody");
Console.WriteLine(name);
}
Если Lookup законно возвращает значение NULL, измените сайт вызова, чтобы принять отсутствующее значение:
public static void AssignmentFixed()
{
string? name = Lookup("somebody");
if (name is not null)
{
Console.WriteLine(name);
}
}
Если Lookup никогда не возвращает значение NULL, измените ее сигнатуру, чтобы вернуть тип ссылок, не допускающий значение NULL. Сценарии, в которых состояние NULL возвращаемого значения зависит от входных данных, см. в следующем разделе по атрибутам null-analysis.
Используйте оператор null-forgiving ! только в тех случаях, когда вы можете гарантировать, что значение не является null, но не можете выразить эту гарантию в системе типов. Каждый ! — это место, где компилятор больше не может вас защитить, поэтому лучше добавить проверку или аннотировать исходный API.
Добавление атрибута null-analysis
Иногда правильное исправление находится не в месте вызова. Подпись метода не фиксирует связь между входными и выходными данными достаточно точно, а компилятор выдает предупреждения внутри в противном случае безопасного кода:
public static bool IsPresent(string? text) =>
!string.IsNullOrEmpty(text);
public static void CallerWithoutAttribute(string? text)
{
if (IsPresent(text))
{
// Warning CS8602: dereference of a possibly null reference.
// The signature doesn't tell the compiler text is not-null here.
Console.WriteLine(text.Length);
}
}
Тело IsPresent доказывает, что аргумент не равен null, когда метод возвращает true, но в сигнатуре это не указано. Добавьте атрибут анализа, допускающий значение NULL , чтобы сделать часть контракта API:
public static bool AttributedIsPresent([NotNullWhen(true)] string? text) =>
!string.IsNullOrEmpty(text);
public static void CallerWithAttribute(string? text)
{
if (AttributedIsPresent(text))
{
// No warning: the attribute tells the compiler text is not-null.
Console.WriteLine(text.Length);
}
}
К общим атрибутам относятся:
- NotNullWhenAttribute — аргумент не равен NULL, если метод возвращает указанное логическое значение.
- NotNullIfNotNullAttribute — возвращаемое значение не равно NULL , если именованный аргумент не имеет значения NULL.
- MemberNotNullAttribute — перечисленные члены не имеют значения NULL после возврата метода.
- DoesNotReturnAttribute — метод никогда не возвращается нормально (например, он всегда вызывает).
Полный список содержит атрибуты статического анализа, допускающие значение NULL.
Инициализация элементов, не допускающих значение NULL
Предупреждение конструктора означает ненулевое поле, свойство или автоматическое свойство (свойство, которое использует поле резервного копирования, созданное компилятором, например public string Name { get; set; }) выходит из конструктора без назначения ненулевого значения:
public class PersonUninitialized
{
// Warning CS8618: Non-nullable property 'Name' is uninitialized.
public string Name { get; set; }
}
Вы можете решить эту проблему несколькими способами. Выберите тот, который лучше всего соответствует вашему намерению проектирования.
Требовать значение в качестве аргумента конструктора. Используйте основной конструктор (параметры, объявленные в самом типе, доступные в тексте) или обычный конструктор, который инициализирует свойство:
public class PersonInjected(string name)
{
public string Name { get; } = name;
}
Задайте свойство required. Вызывающий код должен инициализировать его с помощью инициализатора объектов (синтаксис { Property = value }, следующий за new):
public class PersonRequired
{
public required string Name { get; init; }
}
Инициализируйте со значением по умолчанию. Если у типа есть осмысленное пустое значение, инициализируйте его при объявлении:
public class PersonInitialized
{
public string Name { get; set; } = "John Doe";
}
Tip
Выберите этот метод только в том случае, если тип имеет действительно хорошее значение по умолчанию: тот, который является допустимым, полнофункциональным экземпляром для пользователей. Примеры включают пустые коллекции. Не придумывайте sentinel (специальное значение-заполнитель, например String.Empty, "N/A", "unknown" или -1, которое вы рассматриваете как «нет значения») в качестве замены для null: это убирает предупреждение, но тогда каждый вызывающий код должен знать о sentinel и проверять его, а система типов здесь не поможет. Если не существует хорошего значения по умолчанию, сделайте свойство nullable вместо этого.
Сделайте свойство допускающим значение null. Если значение действительно может быть пропущено, измените тип на nullable:
public class PersonOptional
{
public string? Name { get; set; }
}
Если вспомогательный метод инициализирует поле, пометьте этот метод с помощью MemberNotNullAttribute, чтобы компилятор мог засчитывать обращения к нему.
Проверка параметра проекта
Новые проекты C# позволяют использовать ссылочные типы, допускающие значение NULL, по умолчанию, поэтому большинство кодов, которые вы пишете или читаете, уже включает функцию. Как правило, вам не нужно ничего настраивать. Если вы хотите проверить, включена ли в проекте эта функция, или вам нужно изменить этот параметр, найдите элемент <Nullable> в разделе .csproj:
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
Распространенные поддерживаемые значения: enable (по умолчанию для новых проектов) и disable. Если элемент отсутствует, проект использует значение по умолчанию, заданное SDK и целевой платформой.
Если необходимо включить значение NULL только для части файла с #nullable директивами или использовать частичные warnings и annotations режимы при переносе существующей базы кода, см. стратегии миграции, допускающие значение NULL.
Куда перейти дальше
Если предупреждение не соответствует ни одному из этих шаблонов, в справочной статье по устранению предупреждений, допускающих значение NULL , перечислены методы для каждого предупреждения CS86xxx, который компилятор выдает.
Сведения о планировании миграции, которая постепенно включает типы ссылок, допускающие значение NULL, в существующей базе кода, см. в стратегиях миграции, допускающих значение NULL.