Що означає нуль! вислів означає?


79

Нещодавно я бачив такий код:

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?


16
!є оператором , що прощає нуль , кажучи компілятору, що, хоча він зазвичай цього не дозволяє, він повинен виглядати в інший бік і дозволяти це в будь-якому випадку, тому що ми знаємо краще. null!сам по собі мало практичного використання, оскільки все це, але заперечує корисність онульованих типів посилань. Це корисніше, коли ви знаєте, що вираз не може бути null, але компілятор - ні.
Jeroen

1
@JeroenMostert такий смик, як сила? Я маю на увазі, навіть якщо це незвично, дозволяє робити це примусово.
isxaker

3
Так, за винятком, що це більш ніж незвично - адже stringза новими правилами тип посилання, що підлягає онулюванню, і тому ніколи не повинен бути null. Присвоєння null!ефективно говорить: "Я знаю, цього ніколи не повинно бути null, але вгадайте, я все одно роблю це". Практично немає програми, де це мало б сенс - єдина причина для цього полягала б у тому, що ви знаєте, що збираєтесь присвоїти нецінність, nullперш ніж хтось зможе отримати NullReferenceException, і хочете сигналізувати, що ви не забули призначити це. Можливо, але малоймовірно, тому не дуже вдалий приклад.
Jeroen

Що означає public string? MiddleName {get; } = null;? Чому рядок робить нульовим? Хіба це вже не є нульовим? Або я щось пропустив?
Імад,

2
@JeroenMostert Я знайшов нуль! бути корисними в модульних тестах. Те, що посилання не має бути нульовим, не обов’язково означає, що воно не буде, тому іноді тестування на правильну поведінку в цій ситуації є доречним.
Джессі

Відповіді:


98

Ключем до розуміння того, що null!означає, є розуміння !оператора. Можливо, ви раніше використовували його як оператор "не". Але оскільки C # 8.0, оператор також може бути використаний на типі для контролю Nullabilty

Що таке !оператор при використанні на типі?

!Оператора, коли використовується на типі, називається Null прощає Оператор [ документи ]. Він був введений в 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 - може бути нульовим.
  • Non-Nullable - не може бути нульовим.

Оскільки C # 8.0, всі типи посилань за замовчуванням не мають нульового значення.

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

  • != Від NullableдоNon-Nullable
  • ?= Від Non-NullableдоNullable

Ці оператори в основному є аналогами один одному. Компілятор використовує інформацію - яку ви визначаєте з цими операторами - для забезпечення нульової безпеки.

? Використання оператора.

  1. Допустимий 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і чешеш голову. Оскільки значення фактично стало нульовим після обходу компілятор-перевірок за допомогою !.

Чому тоді існує цей оператор?

  • У деяких крайових випадках компілятор не може виявити, що значення, що допускає нуль, насправді не має значення.
  • Простіше перенесення застарілої кодової бази.
  • У деяких випадках вам просто байдуже, якщо щось стане нульовим.
  • Під час роботи з модульними тестами вам може знадобитися перевірити поведінку коду, коли проходить a null.

Відповідаючи конкретно на ваше запитання.

То що null!означає?

Він повідомляє компілятору, що nullце не nullзначення. Звучить дивно, чи не так?

Це те саме, що y!з прикладу вище. Це виглядає лише дивним, оскільки ви застосовуєте оператор до nullлітералу . Але концепція однакова.

Розбираючи те, що відбувається.

public string LastName { get; } = null!;

Цей рядок визначає властивість класу, що не має дозволу, з іменем LastNameтипу string. Оскільки він не має нульового значення, ви технічно не можете призначити йому null - очевидно.

Але ви тільки що - Присвоїти nullдо LastName- за допомогою !оператора. Тому що null!не є null - наскільки компілятор стурбований нульовою безпекою.


1
рядок може бути нульовим. Ви можете призначити нульове значення, як цей рядок str = null; Я використовую c # 6 .net 4.6.1. Я вважаю, що ваше твердження помилкове. "Цей рядок визначає властивість класу, що не має дозволеного значення, з назвою LastNameof. Рядок типу. Оскільки він не має значення NULL, ви можете технічно не призначати йому значення null - очевидно."
canbax

7
Перевірки @canbax Nullabilty підтримуються лише c # 8 і вище
Patrick Hollweck

3
@canbax a string could be nullбільше не. Я маю на увазі, якщо ви використовуєте c# 8та вмикаєте NullableReferenceTypesфункцію. VS негайно видає попередження, якщо ви спробуєте призначити null string. Але з іншого боку ви можете ввести обнулюваний рядок ( string? s = null). У такому разі жодного попередження.
isxaker

3
"Вам слід намагатися ніколи не використовувати оператор Null-Forgiving-Operator. Він заперечує наслідки нульової безпеки, який вам гарантує компілятор." Як ви збираєтеся писати модульні тести для перевірки аргументів? Це моє найчастіше використання! в Noda Time.
Джон Скіт,

1
Я точно рекомендую редагувати пізніше - в даний час кожен, хто широко використовує оператор, що прощає нуль, у своїх тестах може легко почуватися погано з цього приводу, враховуючи ваш поточний текст, який не вказує на обмеження поради. Я думаю, що "ніколи", швидше за все, буде недосяжним у кількох базах коду - наприклад, у Noda Time у нас є інваріант результатів синтаксичного аналізу, що означає, що я ніколи не виставляю нульове значення, але все одно отримую таке, де сам синтаксичний аналіз не вдався . Я думаю, що "старайся уникати" має більш позитивний тон. Тільки моя думка, хоча.
Джон Скіт,

14

Коли ввімкнено функцію "типи посилань, що допускають нуль", компілятор відстежує, які значення у вашому коді, на його думку, можуть бути нульовими чи ні. Бувають випадки, коли компілятор може мати недостатньо знань.

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

  • якщо ви позначите поле як нульове, компілятор задоволений, але вам доведеться необов'язково перевіряти наявність нуля, коли ви використовуєте поле,
  • якщо залишити поле не нульовим, компілятор скаржиться, що воно не ініціалізоване конструкторами (це можна придушити за допомогою null!), тоді поле можна використовувати без нульової перевірки.

Зверніть увагу, що, використовуючи !оператор придушення, ви берете на себе певний ризик. Уявіть, що ви насправді не ініціалізуєте всі поля так послідовно, як ви думали. Тоді використання null!для ініціалізації поля приховує той факт, що nullпроскакує a . Деякий підозрюючий код може отримати a, nullотже, не вдається.

Більш загально, ви можете мати деякі знання домену: "якщо я перевірив певний метод, то я знаю, що якесь значення не є нульовим":

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

Знову ж таки, ви повинні бути впевнені в інваріанті вашого коду, щоб зробити це ("Я знаю краще").

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.