Что такое NullReferenceException в C# и как её исправить?
Что такое NullReferenceException?
NullReferenceException (исключение нулевой ссылки) возникает, когда вы пытаетесь обратиться к члену объекта (свойству, методу, полю), который равен null. В C# ссылочные типы (классы, строки, массивы, делегаты) могут принимать значение null, означающее "отсутствие объекта". Попытка разыменовать null вызывает исключение.
Как исправить: базовые методы
1. Проверка на null через if
Самый простой способ — явная проверка перед использованием:
if (emp != null)
{
Console.WriteLine(emp.Name);
}
else
{
Console.WriteLine("Сотрудник не найден");
}
2. Оператор условного доступа (?.)
Начиная с C# 6.0, можно использовать оператор ?. (null-conditional operator). Он безопасно обращается к члену, и если объект null, возвращает null:
Console.WriteLine(emp?.Name); // Выведет пустую строку, если emp == null
Для методов: emp?.SomeMethod(); — метод не вызовется, если emp равен null.
3. Оператор объединения с null (??)
Позволяет задать значение по умолчанию, если объект null:
string name = emp?.Name ?? "Неизвестно";
Console.WriteLine(name); // Выведет "Неизвестно", если emp или Name равны null
4. Проверка аргументов методов
В начале методов проверяйте входящие параметры:
public void PrintEmployee(Employee emp)
{
if (emp == null)
throw new ArgumentNullException(nameof(emp));
Console.WriteLine(emp.Name);
}
Продвинутые техники
Null-контекст (nullable reference types)
В C# 8.0+ можно включить nullable-контекст, компилятор будет предупреждать о потенциальных null-ссылках:
#nullable enable
Employee? emp = null; // Вопросительный знак означает, что может быть null
Console.WriteLine(emp!.Name); // Оператор ! подавляет предупреждение (dangerous!)
Console.WriteLine(emp?.Name); // Безопасно
Использование паттерна "Result object"
Вместо возврата null из методов, возвращайте специальный объект результата:
public class Result<T>
{
public bool IsSuccess { get; }
public T Value { get; }
public string Error { get; }
public static Result<T> Success(T value) => new Result<T> { IsSuccess = true, Value = value };
public static Result<T> Failure(string error) => new Result<T> { IsSuccess = false, Error = error };
}
// Использование:
var result = FindEmployee(id);
if (result.IsSuccess)
Console.WriteLine(result.Value.Name);
else
Console.WriteLine($"Ошибка: {result.Error}");
Основные причины NullReferenceException
- Неинициализированные поля — забыли присвоить значение объекту
- Возврат null из методов/свойств — особенно из API или баз данных
- Элементы коллекций — в массиве или списке могут быть null-элементы
- Цепочки вызовов —
obj?.Prop?.Method()без проверки
Отладка
При возникновении исключения Visual Studio подсветит строку. Используйте отладчик (F9 -> F5) и окно Watch, чтобы проверить, какая переменная равна null. Часто помогает разбить длинные цепочки вызовов на отдельные переменные.
Золотое правило: Если метод может вернуть null — документируйте это (через XML-комментарии или nullable-аннотации) и всегда проверяйте результат перед использованием.
Альтернативный подход: Использование шаблона Null Object
Вместо постоянных проверок на null можно создать специальный "пустой" объект, который реализует то же поведение, но безопасно:
public class NullEmployee : Employee
{
public override string Name => "Неизвестный сотрудник";
public override int Age => 0;
}
// Вместо возврата null:
public Employee GetEmployee(int id)
{
var emp = FindFromDatabase(id);
return emp ?? new NullEmployee();
}
Этот паттерн особенно полезен, когда у вас много мест, где используется объект, и вы не хотите везде писать проверки. Минус — нужно создавать отдельный класс для каждого типа.
Логирование и мониторинг
Иногда null приходит из внешних источников (API, файлы, БД). Используйте глобальный обработчик исключений:
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
var ex = (Exception)args.ExceptionObject;
Log.Error(ex, "Unhandled exception");
};
Это поможет не пропустить критичные ошибки, но не заменяет локальные проверки.
Совет: В современных проектах на C# старайтесь использовать nullable reference types и операторы ?./?? — это делает код чище и безопаснее. Но помните: никакая технология не заменит понимания того, какие переменные могут быть null.