← Назад к вопросам

Что такое nullable reference types в C# 8+? Как они помогают избежать NullReferenceException?

2.2 Middle🔥 191 комментариев
#Другое#Основы C# и .NET

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Nullable Reference Types в C# 8.0+

Nullable Reference Types (NRT) — это фича языка C#, представленная в версии 8.0, которая расширяет систему типов для явного разделения ссылочных типов, которые могут содержать null, и тех, которые не должны. Это не изменяет поведение времени выполнения, а добавляет статический анализ на этапе компиляции, чтобы предупреждать о потенциальных ошибках разыменования null.

Как это работает

Компилятор анализирует потоки данных в коде и выдает предупреждения, когда видит потенциальную NullReferenceException. Чтобы включить NRT, нужно активировать nullable-контекст в файле проекта (.csproj):

<PropertyGroup>
    <Nullable>enable</Nullable>
</PropertyGroup>

Или использовать директивы #nullable enable и #nullable disable в коде.

Без NRT (традиционный подход)

Все ссылочные типы неявно nullable, но компилятор не помогает отслеживать null:

string name = null; // Разрешено, но опасно
int length = name.Length; // NullReferenceException во время выполнения!

С NRT

Ссылочные типы по умолчанию не-nullable. Чтобы объявить nullable-переменную, нужно явно добавить ?:

#nullable enable

string nonNullableName = "John"; // Не может быть null
string? nullableName = null;     // Явно nullable

// Предупреждение компилятора: возможное разыменование null
// int length1 = nullableName.Length; 

// Без предупреждения - переменная не-nullable
int length2 = nonNullableName.Length; 

Как NRT помогают избежать NullReferenceException

  1. Статический анализ потока данных
    Компилятор отслеживает, может ли переменная быть null в каждой точке кода:
string? GetName(int id) => id > 0 ? "User" : null;

void ProcessUser() {
    string? name = GetName(0);
    
    // Предупреждение: name может быть null здесь
    // Console.WriteLine(name.Length);
    
    if (name != null) {
        // Компилятор понимает, что name не-null здесь
        Console.WriteLine(name.Length); // Без предупреждений
    }
}
  1. Аннотации для более точного анализа
    Можно использовать атрибуты для указания поведения методов:
public void Process([NotNull] string input) {
    // Компилятор считает input не-null внутри метода
    Console.WriteLine(input.Length);
}

public string? GetValueOrDefault([AllowNull] string defaultValue) {
    // defaultValue может быть null
    return defaultValue ?? "default";
}
  1. Операторы подавления и проверки
    • ! (null-forgiving operator) — явно указывает компилятору, что значение не-null (используйте осторожно!):
string? possiblyNull = GetName();
string definitelyNotNull = possiblyNull!; // Подавляем предупреждение
  • ?? и ??= — предоставляют значения по умолчанию:
string? nullableValue = GetPossiblyNullValue();
string safeValue = nullableValue ?? "default";
  1. Строгие контракты для API
    При использовании NRT сигнатуры методов явно указывают, возвращают ли они nullable-значения:
public string GetRequiredData() => /* всегда возвращает строку */;
public string? GetOptionalData() => /* может вернуть null */;

Это делает API самодокументируемым и предотвращает ошибки на этапе компиляции.

Преимущества и ограничения

Преимущества:

  • Раннее обнаружение потенциальных NullReferenceException
  • Улучшенная документация кода через типы
  • Лучшая поддержка рефакторинга
  • Совместимость с существующим кодом (можно включать постепенно)

Ограничения:

  • Это статический анализ, а не проверка времени выполнения
  • Ложные срабатывания в сложных сценариях
  • Требует дисциплины при работе со сторонними библиотеками без NRT
  • Не защищает от явного присваивания null не-nullable полям через рефлексию

Практический пример

#nullable enable

public class UserService {
    private readonly IUserRepository _repository;
    
    // Поле инициализируется в конструкторе - не-nullable
    public UserService(IUserRepository repository) {
        _repository = repository ?? throw new ArgumentNullException();
    }
    
    public string GetUserName(int userId) {
        // Возвращаемый тип не-nullable - метод не должен возвращать null
        User? user = _repository.FindUser(userId); // FindUser возвращает User?
        
        if (user == null) {
            return "Unknown User"; // Все пути возвращают значение
        }
        
        return user.Name; // Name - не-nullable свойство
    }
}

public class User {
    public string Name { get; } // Не-nullable свойство
    
    public User(string name) {
        Name = name ?? throw new ArgumentNullException(nameof(name));
    }
}

Заключение

Nullable Reference Types — это мощный инструмент для повышения надежности кода на C#. Они не устраняют NullReferenceException полностью, но переносят обнаружение многих таких ошибок со времени выполнения на этап компиляции. Это требует дополнительных усилий при разработке (явно указывать nullable-типы, обрабатывать потенциальные null-значения), но значительно снижает количество ошибок в production. Для максимальной эффективности NRT следует использовать вместе с другими практиками: проверкой аргументов, использованием шаблона Null Object и корректной обработкой исключений.

Что такое nullable reference types в C# 8+? Как они помогают избежать NullReferenceException? | PrepBro