JavaRush /Курси /C# SELF /Вступ до Nullable Reference Types

Вступ до Nullable Reference Types

C# SELF
Рівень 14 , Лекція 1
Відкрита

1. Проблема NullReferenceException

Майже кожен початківець (і не тільки) програміст C# хоча б раз зустрічався з цим тривожним повідомленням:


System.NullReferenceException: Object reference not set to an instance of an object.

Ось класика жанру:

string hello = null;
Console.WriteLine(hello.Length); // БАМ! NullReferenceException

Суть проста: ви намагаєтеся звернутися до об’єкта, якого не існує. Тобто змінна посилається на null — порожнє місце замість об’єкта.

Чому помилку легко допустити?

Адже більшість посилальних типів у C# історично могли приймати значення null. Розробники часто забували перевірити, що змінна справді вказує на об’єкт, і внаслідок цього отримували знамениту NullReferenceException.

Якби розробник отримував долар за кожен NullReferenceException, давно б написав власну операційну систему.

Чому це проблема?

Раніше ми навчилися робити так, щоб значення типу int? або double? могли дорівнювати null — для випадків, коли треба явно позначити «відсутність значення» (наприклад, поле в базі даних може бути не заповнене).

Але посилальні типи (наприклад, string, будь-які класи, масиви тощо) завжди могли дорівнювати null. Так було від самого початку існування C#. Це зручно, але й небезпечно, адже мова не змушувала нас явно думати: «А чи може це посилання бути порожнім?»

2. Еволюція: Nullable Reference Types (NRT)

П’ять років тому з’явилася можливість, що докорінно змінила підхід до боротьби з NullReferenceExceptionNullable Reference Types (NRT).

string s = "hello";   // not-nullable reference
string? maybe = null; // nullable reference
Різниця між not-nullable і nullable посилальними типами

Головна ідея:

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

У нових версіях C# змінився підхід до оголошень змінних-посилань: за замовчуванням не можна присвоювати null, якщо ви цього явно не дозволили.

Лише один символ ? — і сенс змінної одразу змінюється!

3. Увімкнення Nullable Reference Types

За замовчуванням такий суворий синтаксис вимкнений у більшості проєктів, щоб не зламати наявний код. Але сучасні шаблони проєктів у Visual Studio, Rider та .NET CLI вже створюють проєкти з увімкненими NRT — або принаймні наполегливо радять це зробити.

Щоб точно дізнатися, чи увімкнені NRT у вашому проєкті, знайдіть у файлі .csproj рядок:

<Nullable>enable</Nullable>
Увімкнення NRT у проєкті

Якщо цього рядка немає, можна додати його вручну — це безпечно.

Як це впливає на код?

  • Якщо NRT вимкнені (старий режим): string s = null; — жодних помилок; компілятор не заперечує.
  • Якщо увімкнені: компілятор почне попереджати, якщо ви намагаєтеся присвоїти null там, де його не має бути.

4. Приклади з NRT

Простий приклад


#nullable enable // Цей рядок вмикає перевірку NRT для цього файлу

string notNullable = "Привіт";
string notNullable2 = null; // ПОМИЛКА компіляції!
string? nullableString = null; // Усе гаразд: ми явно дозволили null
Приклад суворої перевірки на null

У першому рядку ми оголосили рядок, що завжди має посилатися на реальний об’єкт.
У третьому — оголосили рядок, який може бути null.

Перевірка на null

void PrintLength(string? s)
{
    // Компілятор попередить: "А що, якщо s == null?"
    Console.WriteLine(s.Length); 
    
    // А так - добре
    if (s != null)
    {
        Console.WriteLine(s.Length); 
    }
}

Компілятор тепер допомагає не забути про перевірки!

Попередження компілятора

Якщо ви проігноруєте попередження і все ж звернетеся до nullable-змінної без перевірки — отримаєте нове (дуже корисне!) попередження компілятора. Це не помилка (проєкт усе одно збереться), але жовта «лампочка» натякне: «Ви певні?»

Порівнюємо режими роботи:

Тип C# Може бути null? Класичний режим (до NRT) Режим NRT (#nullable enable)
int Ні Ні Ні
int? Так Так Так
string Так Ні (завжди можна) Ні (за замовчуванням не можна)
string? Так Ні Так

5. Поради: як і навіщо використовувати NRT

  • Менше помилок: Менше неочікуваних падінь через null, більше радості для розробника й користувача.
  • Ясність коду: Одразу видно, що може бути порожнім, а що — завжди має бути заповнене.
  • Допомога від компілятора: Варто визнати: він справді працює на нашу користь. Попередження NRT — цінне джерело інформації про потенційні помилки.

Де це незамінно

  • У великих проєктах, де багато людей працюють з одним кодом.
  • В API та публічних бібліотеках — щоб чітко вказати іншим, що дозволено, а що ні.
  • Там, де важлива надійність (наприклад, банківські застосунки, медичні системи тощо).

6. Типові помилки та підводні камені

  • «Забули поставити ?»
    Присвоюєте null звичайному рядку (string s = null;) — і компілятор повідомляє про помилку, адже за замовчуванням звичайний рядок не може бути null.
  • «Перебільшили з ?»
    Позначаєте всі змінні як string?, аби компілятор не попереджав. Проте сенс підходу в тому, щоб уважно позначати саме ті місця, де допускається порожнє значення.
  • «Неправильно інтерпретували попередження»
    Ігноруєте попередження, а потім отримуєте NullReferenceException саме там, де сподівалися, що компілятор убереже.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ