Что значит ноль! выражение значит?

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

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;
    }
}

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

Ответ 1

Ключ к пониманию, что null! значит это понимание ! оператор. Возможно, вы использовали его раньше как оператор "не". Но, начиная с С# 8.0, оператор также может использоваться в типе для управления Nullabilty

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

! Оператор, используемый в типе, называется оператором Null Forgiving [ docs ]. Это было введено в С# 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 - может быть нулевым
  • Не обнуляемый - не может быть нулевым.

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

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

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

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

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

  1. Обнуляемая string? x; string? x;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Он сообщает компилятору, что null не является null значением. Звучит странно, не правда ли?

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

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

public string LastName { get; } = null!;

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

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

Ответ 2

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

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

  • если вы пометите поле как обнуляемое, компилятор будет счастлив, но вам не обязательно проверять наличие нуля при использовании поля,
  • если вы оставите поле как необнуляемое, компилятор будет жаловаться, что оно не инициализировано конструкторами (вы можете подавить это с помощью 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!);
}

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