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)
П’ять років тому з’явилася можливість, що докорінно змінила підхід до боротьби з NullReferenceException — Nullable Reference Types (NRT).
string s = "hello"; // not-nullable reference
string? maybe = null; // nullable reference
Головна ідея:
- Чітко розділити змінні-посилання, які не можуть дорівнювати null, і ті, що можуть бути порожніми.
- Допомагати розробнику бачити потенційні проблеми з null ще на етапі компіляції, а не під час виконання програми (коли вже пізно).
У нових версіях C# змінився підхід до оголошень змінних-посилань: за замовчуванням не можна присвоювати null, якщо ви цього явно не дозволили.
Лише один символ ? — і сенс змінної одразу змінюється!
3. Увімкнення Nullable Reference Types
За замовчуванням такий суворий синтаксис вимкнений у більшості проєктів, щоб не зламати наявний код. Але сучасні шаблони проєктів у Visual Studio, Rider та .NET CLI вже створюють проєкти з увімкненими NRT — або принаймні наполегливо радять це зробити.
Щоб точно дізнатися, чи увімкнені NRT у вашому проєкті, знайдіть у файлі .csproj рядок:
<Nullable>enable</Nullable>
Якщо цього рядка немає, можна додати його вручну — це безпечно.
Як це впливає на код?
- Якщо NRT вимкнені (старий режим): string s = null; — жодних помилок; компілятор не заперечує.
- Якщо увімкнені: компілятор почне попереджати, якщо ви намагаєтеся присвоїти null там, де його не має бути.
4. Приклади з NRT
Простий приклад
#nullable enable // Цей рядок вмикає перевірку NRT для цього файлу
string notNullable = "Привіт";
string notNullable2 = null; // ПОМИЛКА компіляції!
string? nullableString = 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 саме там, де сподівалися, що компілятор убереже.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ