Разрешение предупреждений, допускающих значения NULL

В этой статье рассматриваются следующие предупреждения компилятора:

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

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

  • Добавление необходимых проверка null.
  • Добавление ? или ! пустые заметки.
  • Добавление атрибутов, описывающих семантику NULL.
  • Инициализация переменных правильно.

Возможная разыменовка null

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

  • Cs8602 - Dereference возможно null reference.
  • Объект CS8670 - или инициализатор коллекции неявно разыменовывание, возможно, null-член.

Следующий код демонстрирует один пример каждого из предыдущих предупреждений:

class Container
{
    public List<string>? States { get; set; }
}

internal void PossibleDereferenceNullExamples(string? message)
{
    Console.WriteLine(message.Length); // CS8602

    var c = new Container { States = { "Red", "Yellow", "Green" } }; // CS8670
}

В приведенном выше примере предупреждение связано с тем, что Containerпараметр , cможет иметь значение NULL для States свойства. Назначение новых состояний коллекции, которая может быть null, вызывает предупреждение.

Чтобы удалить эти предупреждения, необходимо добавить код для изменения состояния NULL этой переменной на not-NULL перед отменой его ссылки. Предупреждение инициализатора коллекции может быть труднее обнаружить. Компилятор обнаруживает, что коллекция может быть null , когда инициализатор добавляет в него элементы.

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

void WriteMessageLength(string? message)
{
    if (message is not null)
    {
        Console.WriteLine(message.Length);
    }
    
}

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

class Container
{
    public List<string> States { get; } = new();
}

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

public void WriteMessage(string? message)
{
    if (IsNotNull(message))
        Console.WriteLine(message.Length);
}

Компилятор предупреждает, что при записи свойства message.Length может быть разоменовывание null, так как его статический анализ определяет, что message может быть null. Возможно, вы знаете, что IsNotNull предоставляет значение NULL проверка, и когда возвращаетсяtrue, состояниеmessage NULL должно быть не равно NULL. Необходимо сообщить компилятору эти факты. Одним из способов является использование оператора пустого прощения. ! Инструкцию можно изменить, чтобы она соответствовала WriteLine следующему коду:

Console.WriteLine(message!.Length);

Оператор прощения null делает выражение не null, даже если оно было, возможно, null без ! применения. В этом примере лучше добавить атрибут в сигнатуру IsNotNull:

private static bool IsNotNull([NotNullWhen(true)] object? obj) => obj != null;

Сообщает System.Diagnostics.CodeAnalysis.NotNullWhenAttribute компилятору, что аргумент, используемый для obj параметра, не имеет значения NULL при возврате trueметода. При возврате falseметода аргумент имеет то же состояние NULL, которое было до вызова метода.

Совет

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

Исправление предупреждения для разыменовки переменной , возможно, null , включает в себя один из трех методов:

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

Возможное значение NULL, назначенное ненулевой ссылке

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

  • Вызываемое значение CS8597 - может иметь значение NULL.
  • CS8600 - Преобразование литерала NULL или возможного значения NULL в тип, не допускающий значение NULL.
  • Возможное назначение ссылок на null CS8601 - .
  • Возврат возможной ссылки НА NULL CS8603 - .
  • Допустимый ссылочный аргумент CS8604 - для параметра.
  • CS8605 - Распаковка возможно null-значения.
  • CS8625 - Не удается преобразовать литерал NULL в ненулевой ссылочный тип.
  • Тип значения NULL CS8629 - может иметь значение NULL.

Компилятор выдает эти предупреждения при попытке назначить выражение, которое может иметь значение NULL переменной, которая является ненулевой. Например:

string? TryGetMessage(int id) => "";

string msg = TryGetMessage(42);  // Possible null assignment.

Различные предупреждения указывают на предоставление сведений о коде, таких как назначение, распаковка назначения, операторы возврата, аргументы методов и вызов выражений.

Чтобы устранить эти предупреждения, можно выполнить одно из трех действий. Один из них заключается в добавлении заметки ? , чтобы сделать переменную типом ссылок, допускающей значение NULL. Это изменение может привести к другим предупреждениям. Изменение переменной из ненулевой ссылки на ссылку, допускающую значение NULL, изменяет состояние NULL по умолчанию с ненулевого на может быть null. Статический анализ компилятора может находить экземпляры, в которых разыменовывается переменная, которая может иметь значение NULL.

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

string notNullMsg = TryGetMessage(42) ?? "Unknown message id: 42";

В предыдущих примерах показано назначение возвращаемого значения метода. Вы можете заметить метод (или свойство), чтобы указать, когда метод возвращает ненульное значение. Часто System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute указывается, что возвращаемое значение не равно NULL, если входной аргумент не имеет значения NULL. Другой альтернативой является добавление оператора пустого прощения в ! правую сторону:

string msg = TryGetMessage(42)!;

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

  • Измените левую сторону назначения на тип, допускающий значение NULL. Это действие может привести к новым предупреждениям при разыменовании этой переменной.
  • Предоставьте значение NULL-проверка перед назначением.
  • Заметите API, который создает правую сторону назначения.
  • Добавьте оператор пустого прощения в правую сторону назначения.

Ненуклимая ссылка не инициализирована

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

  • Переменная CS8618 - , не допускающая значение NULL, должна содержать ненулевое значение при выходе из конструктора. Рассмотрите возможность объявления его как допускающего значение NULL.
  • Параметр CS8762 - должен иметь ненулевое значение при выходе.

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

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

Инициализация FirstName не LastName гарантируется. Если этот код новый, рассмотрите возможность изменения общедоступного интерфейса. Приведенный выше пример можно обновить следующим образом:

public class Person
{
    public Person(string first, string last)
    {
        FirstName = first;
        LastName = last;
    }

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

Если требуется создать Person объект перед заданием имени, можно инициализировать свойства с помощью значения, отличного от NULL по умолчанию:

public class Person
{
    public string FirstName { get; set; } = string.Empty;
    public string LastName { get; set; } = string.Empty;
}

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

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

Существующий код может потребовать других изменений, чтобы сообщить компилятору о семантике NULL для этих элементов. Возможно, вы создали несколько конструкторов, и ваш класс может иметь частный вспомогательный метод, который инициализирует один или несколько членов. Код инициализации можно переместить в один конструктор и убедиться, что все конструкторы вызывают его с общим кодом инициализации. Кроме того, можно использовать System.Diagnostics.CodeAnalysis.MemberNotNullAttribute атрибуты и System.Diagnostics.CodeAnalysis.MemberNotNullWhenAttribute атрибуты. Эти атрибуты сообщают компилятору, что член не имеет значения 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, чтобы указать, что член инициализирован в другом коде. Например, рассмотрим следующие классы, представляющие модель Entity Framework Core:

public class TodoItem
{
    public long Id { get; set; }
    public string? Name { get; set; }
    public bool IsComplete { get; set; }
}

public class TodoContext : DbContext
{
    public TodoContext(DbContextOptions<TodoContext> options)
        : base(options)
    {
    }

    public DbSet<TodoItem> TodoItems { get; set; } = null!;
}

Свойство DbSet инициализировано значением параметра null!. Это сообщает компилятору, что для свойства задано значение без значения NULL . Фактически база DbContext выполняет инициализацию набора. Статический анализ компилятора не выбирает это. Дополнительные сведения о работе с ссылочными типами, допускаемыми значением NULL, и Entity Framework Core см. в статье о работе с типами ссылок, допускающих значение NULL, в EF Core.

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

  • Измените конструкторы или инициализаторы полей, чтобы обеспечить инициализацию всех ненуклюжих элементов.
  • Измените один или несколько элементов на типы, допускающие значение NULL.
  • Заметите все вспомогательные методы, чтобы указать, какие члены назначены.
  • Добавьте инициализатор, чтобы null! указать, что элемент инициализирован в другом коде.

Несоответствие в объявлении доступности NULL

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

  • Значение NULL типов ссылок в типе CS8608 - не соответствует переопределенным элементу.
  • Значение CS8609 - Nullability ссылочных типов в возвращаемом типе не соответствует переопределенным элементу.
  • Значение CS8610 - Nullability ссылочных типов в параметре типа не соответствует переопределенным элементу.
  • Значение CS8611 - Nullability ссылочных типов в параметре типа не соответствует объявлению частичного метода.
  • Значение NULL типов ссылок в типе CS8612 - не соответствует неявно реализованным элементу.
  • Значение NULL типов ссылок в возвращаемом типе CS8613 - не соответствует неявно реализованным элементу.
  • Значение CS8614 - Nullability ссылочных типов в типе параметра не совпадает с неявно реализованным членом.
  • Значение NULL типов ссылок в типе CS8615 - не совпадает с реализованным элементом.
  • Значение CS8616 - Nullability ссылочных типов в возвращаемом типе не соответствует реализованным элементу.
  • Значение CS8617 - Nullability ссылочных типов в типе параметра не соответствует реализованным элементу.
  • Значение CS8619 - Nullability ссылочных типов в значении не соответствует целевому типу.
  • Аргумент CS8620 - нельзя использовать для параметра из-за различий в допустимости значений NULL ссылочных типов.
  • Значение NULL типов ссылок в возвращаемом типе CS8621 - не соответствует целевому делегату (возможно, из-за атрибутов nullability).
  • Значение CS8622 - Nullability ссылочных типов в типе параметра не соответствует целевому делегату (возможно, из-за атрибутов null).
  • Аргумент CS8624 - нельзя использовать в качестве выходных данных из-за различий в допустимости значений NULL ссылочных типов.
  • CS8631 - Тип нельзя использовать в качестве параметра типа в универсальном типе или методе. Значение NULL аргумента типа не соответствует типу ограничения.
  • Значение CS8633 - Nullability в ограничениях для параметра типа метода не соответствует ограничениям для параметра типа метода интерфейса. Вместо этого рекомендуется использовать явную реализацию интерфейса.
  • CS8634 - Тип нельзя использовать в качестве параметра типа в универсальном типе или методе. Значение NULL аргумента типа не соответствует ограничению class.
  • Значение NULL типов ссылок в явном описателье интерфейса CS8643 - не соответствует интерфейсу, реализованном типом.
  • Тип CS8644 - не реализует член интерфейса. Значение NULL ссылочных типов в интерфейсе, реализованном базовым типом, не соответствует.
  • Элемент CS8645 - уже указан в списке интерфейсов по типу с разными значениями NULL ссылочных типов.
  • Объявления частичного метода CS8667 - имеют несогласованность null в ограничениях для параметра типа.
  • CS8714 - Тип нельзя использовать в качестве параметра типа в универсальном типе или методе. Значение NULL аргумента типа не соответствует ограничению notnull.
  • Значение NULL типа возвращаемого значения CS8764 - не совпадает с переопределенным элементом (возможно, из-за атрибутов nullability).
  • Значение NULL типа параметра CS8765 - не совпадает с переопределенным элементом (возможно, из-за атрибутов nullability).
  • Значение CS8766 - Nullability ссылочных типов в возвращаемом типе не совпадает с неявно реализованным элементом (возможно, из-за атрибутов nullability).
  • Значение NULL типов ссылок в типе параметра CS8767 - не совпадает с неявно реализованным элементом (возможно, из-за атрибутов nullability).
  • Cs8768 - Nullability ссылочных типов в возвращаемом типе не совпадает с реализованным элементом (возможно, из-за атрибутов nullability).
  • Значение NULL типов ссылок в типе параметра CS8769 - не соответствует реализованным элементу (возможно, из-за атрибутов nullability).
  • Значение NULL типов ссылок в возвращаемом типе CS8819 - не соответствует объявлению частичного метода.

Следующий код демонстрирует CS8764:

public class B
{
    public virtual string GetMessage(string id) => string.Empty;
}
public class D : B
{
    public override string? GetMessage(string? id) => default;
}

В предыдущем примере показан virtual метод в базовом классе и override с разными значениями NULL. Базовый класс возвращает непустую строку, но производный класс возвращает строку, допускаемую значение NULL. string Если и string? обратное, это будет разрешено, так как производный класс является более строгим. Аналогичным образом объявления параметров должны совпадать. Параметры в методе переопределения могут разрешать значение NULL, даже если базовый класс не имеет значения.

Другие ситуации могут создавать эти предупреждения. У вас может быть несоответствие в объявлении метода интерфейса и реализации этого метода. Или тип делегата и выражение для этого делегата может отличаться. Параметр типа и аргумент типа могут отличаться в допустимости null.

Чтобы устранить эти предупреждения, обновите соответствующее объявление.

Код не соответствует объявлению атрибутов

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

  • CS8607 - Возможное значение NULL не может использоваться для типа, помеченного или [NotNull][DisallowNull]
  • Метод CS8763 - , помеченный [DoesNotReturn] как не должен возвращать.
  • Метод CS8770 - не имеет примечаний [DoesNotReturn] для сопоставления реализованного или переопределенного элемента.
  • Элемент CS8774 - должен иметь ненулевое значение при выходе.
  • Элемент CS8775 - должен иметь ненулевое значение при выходе.
  • Член CS8776 - нельзя использовать в этом атрибуте.
  • Параметр CS8777 - должен иметь ненулевое значение при выходе.
  • Параметр CS8824 - должен иметь ненулевое значение при выходе, так как параметр не имеет значения NULL.
  • Возвращаемое значение CS8825 - должно быть не null, так как параметр не имеет значения NULL.

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

public bool TryGetMessage(int id, [NotNullWhen(true)] out string? message)
{
    message = null;
    return true;

}

Компилятор выдает предупреждение, так как message параметр назначается nullи метод возвращается true. Атрибут NotNullWhen указывает, что не должно произойти.

Чтобы устранить эти предупреждения, обновите код, чтобы он соответствовал ожиданиям примененных атрибутов. Вы можете изменить атрибуты или алгоритм.

Исчерпывающее выражение коммутатора

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

  • CS8655 - Выражение коммутатора не обрабатывает некоторые входные данные NULL (это не является исчерпывающим).
  • CS8847 - Выражение коммутатора не обрабатывает некоторые входные данные NULL (это не является исчерпывающим). Однако шаблон с предложением "when" может успешно соответствовать этому значению.

В следующем примере кода демонстрируется следующее условие:

int AsScale(string status) =>
    status switch
    {
        "Red" => 0,
        "Yellow" => 5,
        "Green" => 10,
        { } => -1
    };

Входное выражение — это string, а не string?. Компилятор по-прежнему создает это предупреждение. Шаблон { } обрабатывает все ненулевое значения, но не соответствует null. Чтобы устранить эти ошибки, можно добавить явный null случай или заменить { }_ шаблоном (dis карта). Шаблон dis карта соответствует null, а также любому другому значению.