Что значит ноль! заявление значит?

Я недавно видел следующий код:

public class Person
{
    //line 1
    public string FirstName { get; }
    //line 2
    public string LastName { get; } = null!;
    //assign null is possible
    public string? MiddleName {get; } = null;

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

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

В основном я пытаюсь покопаться в новых возможностях C# 8. Один из них является NullableReferenceTypes, На самом деле уже много статей и информации об этом. Например, эта статья довольно хорошая. Но я не нахожу никакой информации об этом новом заявлении null!Может ли кто-нибудь дать мне объяснение этому? Почему я должен использовать это? И какая разница между line1 а также line2?

7 ответов

Решение

Что ! оператор при использовании на тип?

! Оператор, используемый в типе, называется оператором Null Forgiving [ docs ]. Это было введено в C# 8.0


Техническое объяснение

Типичное использование

Предполагая это определение:

class Person
{
  public string? MiddleName;
}

Использование будет:

void LogPerson(Person person)
{
    Console.WriteLine(person.MiddleName.Length);  // WARNING: may be null
    Console.WriteLine(person.MiddleName!.Length); // No warning
}

Этот оператор в основном отключает нулевые проверки компилятором.

Внутренние работы

Использование этого оператора сообщает компилятору, что что-то, что может быть нулевым, безопасно для доступа. Вы выражаете намерение "не заботиться" о нулевой безопасности в этом случае.

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

  • Nullable - может быть нулевым
  • Не обнуляемый - не может быть нулевым.

Начиная с C# 8.0 все ссылочные типы по умолчанию не обнуляются.

"Обнуляемость" может быть изменена этими 2 новыми операторами типа:

  • ! = От Nullable в Non-Nullable
  • ? = От Non-Nullable в Nullable

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

? Использование оператора.

  1. Nullable string? x;

    • x является ссылочным типом - поэтому по умолчанию не обнуляется.
    • Мы применяем ? оператор - что делает его обнуляемым.
    • x = null Работает отлично.
  2. Ненулевые string y;

    • y является ссылочным типом - поэтому по умолчанию не обнуляется.
    • x = null Создает предупреждение, поскольку вы назначаете нулевое значение чему-то, что не должно быть нулевым.

! Использование оператора.

string x;
string? y = null;
  1. x = y

    • Нелегальная! - Warning: "y" may be null
    • Правая сторона задания не допускает обнуления, но левая сторона обнуляется.
  2. x = y!

    • Юридическая!
    • Правая и левая сторона задания не могут быть обнулены.
    • Работает с y! Применяет ! оператор для y что делает его необнуляемым.

ВНИМАНИЕ ! Оператор отключает только проверки компилятора на уровне системы типов - во время выполнения значение все еще может быть нулевым.

Это анти-паттерн.

Вы должны стараться избегать использования ! Null-прощающий-оператор.

Существуют допустимые варианты использования (подробно описанные ниже), такие как модульные тесты, в которых этот оператор подходит для использования. В 99% случаев вам лучше альтернативное решение. Пожалуйста, не шлепайте десятки ! в вашем коде, просто чтобы заставить замолчать предупреждения. Подумайте, действительно ли ваше дело оправдывает использование.

Используйте - но с осторожностью. Если нет конкретной цели / варианта использования, предпочитайте не использовать его.

Это сводит на нет эффекты нулевой безопасности, которые вы гарантируете компилятором.

С использованием ! Оператор создаст очень трудно найти ошибки. Если у вас есть свойство, которое помечено как необнуляемое, вы будете считать, что можете безопасно его использовать. Но во время выполнения вы неожиданно сталкиваетесь с NullReferenceException и почесать голову. Так как значение фактически стало нулевым после обхода проверок компилятором с !,

Почему этот оператор существует тогда?

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

Отвечая на ваш вопрос конкретно.

Так что же null! имею в виду?

Это говорит компилятору, что null это не null значение. Звучит странно, не правда ли?

Это так же, как y! из примера выше. Это выглядит странно, так как вы применяете оператор к null буквальный. Но концепция та же самая.

Разбирая, что происходит.

public string LastName { get; } = null!;

Эта строка определяет ненулевое свойство класса с именем LastName типа string, Так как он не обнуляем, вы технически не можете присвоить ему ноль - очевидно.

Но вы делаете это, используя ! оператор. Так как null! не является нулевым - насколько компилятор обеспокоен нулевой безопасностью.

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

Я использую Entity Framework Core для отметки переменных, инициализированных посредством отражения:

      public class MyDbContext : DbContext
{
    public DbSet<Entity> Entities { get; set; } = null!; // Assigned by EF Core
    public DbSet<Person> People { get; set; } = null!; // Assigned by EF Core
}

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

      public class MyUnitTests
{
    IDatabaseRepository _repo = null!;

    [OneTimeSetUp]
    public void PrepareTestDatabase()
    {
        ...
        _repo = ...
        ...
    }
}

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

Когда включена функция "Обнуляемые ссылочные типы", компилятор отслеживает, какие значения в вашем коде могут быть нулевыми или нет. Есть моменты, когда компилятор может иметь недостаточные знания.

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

  • если вы пометите поле как обнуляемое, компилятор будет счастлив, но вам не обязательно проверять наличие нуля при использовании поля,
  • если вы оставите поле как необнуляемое, компилятор будет жаловаться, что оно не инициализировано конструкторами (вы можете подавить это с помощью null!), то поле можно использовать без проверки нуля.

Обратите внимание, что с помощью ! оператор подавления, вы берете на себя некоторый риск. Представьте, что вы на самом деле инициализируете не все поля так, как вы думали. Тогда использование null! инициализировать поле скрывает тот факт, что null проскальзывает. Некоторый ничего не подозревающий код может получить null и поэтому потерпеть неудачу.

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

if (CheckEverythingIsReady())
{
   // you know that `field` is non-null, but the compiler doesn't. The suppression can help
   UseNonNullValueFromField(this.field!);
}

Опять же, вы должны быть уверены в инварианте вашего кода, чтобы сделать это ("Я знаю лучше").

Я не думаю, что этот вопрос можно обсуждать без использования конкретной версии С#.

      public string RpyDescription { get; set; } = null!;

Это распространено в .NET 6 Core... на самом деле это необходимо, если вы хотите описать строку как не допускающую значение NULL. Одной из причин этого является то, что вы работаете с базами данных SQL, где для поля может быть установлено значение «Разрешить нуль». Тем более при работе со структурами JSON, которые принимают null. ЭФ должен знать.

Тип ссылки (куча — указатель на ячейку памяти, где хранятся данные) или тип значения (стек — ячейка памяти, где хранятся данные).

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

В EF/Core очень важно понимать взаимосвязь между нулевым значением базы данных и нулевым значением модели/объектов.

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

Вы также можете:

  1. Отметьте свойства как допускающие значение NULL и везде применяйте проверки на значение NULL.
  2. Установите значение по умолчанию""что позволит свойству конфигурации быть неопределенным, а затем проверить наличиеstring.IsNullOrEmpty()всякий раз, когда он потребляется.
  3. Установите значение по умолчанию какnull!, который отключает предупреждение IDE и заставит приложение завершить работу при запуске конфигурации, если свойство не установлено.

Этот вопрос необходимо обновить, в С# 10 в объектно-реляционном сопоставлении этот оператор в сочетании с? оператор имеет решающее значение, это способ сообщить другим кодерам о проекте, будущим кодерам, и напомнить себе, как данные будут использоваться в конечном итоге, и нулевые правила в отношении этих данных.

      public string Data { get; set; } = null!;
public string? Nullable { get; set; }

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

Недостатком является то, что если вы создаете экземпляр класса и не имеете информации, необходимой для создания экземпляров значений NOT NULL, вы получите странные значения по умолчанию, которые не всегда имеют смысл. Во-первых, это происходит не так часто, как думают люди, и часто происходит из-за ленивого программирования. Когда вы создаете экземпляр класса. Вы должны вычислить или присвоить эти свойства NOT NULL. Сразу. Если вы объявляете автомобиль, и в вашей базе данных или API у автомобилей есть колеса, если вы объявляете автомобиль, не присваивая колесу значения свойств.

Тогда вы действительно создавали машину осмысленным образом? Вы, конечно, не определили автомобиль так, как его понимает база данных, это автомобиль без колес, и так быть не должно, это может быть удобно, но это противоречит основным принципам объектно-ориентированного программирования.

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

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

Я бы даже сказал, что когда дело доходит до строк, этот оператор следует использовать в подавляющем большинстве случаев. Когда мы объявляем int, bool или float, мы понимаем, что они не могут быть нулевыми, если мы не укажем это явно. С какой стати вы выступаете за другое правило для строк!?тот факт, что раньше мы не могли сделать это со строками, был конструктивным недостатком.

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

Например:

      if (".js".Equals(Path.GetExtension(@"D:\file.js")))
{
    var directory = Path.GetDirectoryName(includeFile);
    var map = Path.Combine(
        directory, // Compiler: Possible 'null' assignment to nun-nullable entity
        "file.map");
}

Вот как это исправляется:

      if (".js".Equals(Path.GetExtension(@"D:\file.js")))
{
    var directory = Path.GetDirectoryName(includeFile);
    var map = Path.Combine(
        directory!, // The warning is ignored because we are sure the directory variable is not null
        "file.map");
}

Бывают и другие случаи, например, когда в модульном тесте мы намеренно отправляем нулевое значение функции, которая не принимает нулевой аргумент.
Например:

      public class Person
{
    public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));

    public string Name { get; }
}

и мы тестируем:

      [TestMethod, ExpectedException(typeof(ArgumentNullException))]
public void NullNameShouldThrowTest()
{
    var person = new Person(null!);
}
Другие вопросы по тегам