Введение: Почему ошибки — это нормально
Путь каждого разработчика начинается с ошибок. C# (.NET) — мощный и строго типизированный язык, который прощает меньше, чем динамические языки, но взамен даёт надёжность и производительность. В этой статье мы разберём 4 самых частых граблей, на которые наступают новички, и покажем, как их обойти. Статья будет полезна как тем, кто только начал изучать C#, так и тем, кто хочет освежить знания.
1. Игнорирование null и NullReferenceException
Самая популярная ошибка в C# — NullReferenceException. Она возникает, когда вы пытаетесь обратиться к члену объекта, который равен null.
Пример проблемы
string text = null;
int length = text.Length; // Ошибка! NullReferenceException
Начинающие часто забывают проверять значения, возвращаемые из методов, или полагаются на то, что объект точно существует.
Как правильно
- Всегда проверяйте объекты на
nullперед использованием. - Используйте операторы
?.(null-conditional) и??(null-coalescing).
string text = GetText(); // может вернуть null
int? length = text?.Length; // если text == null, length будет null
int safeLength = text?.Length ?? 0; // если null, то 0
Console.WriteLine(safeLength);
Также в C# 8.0+ появились nullable reference types, которые включаются через #nullable enable. Они заставляют вас явно указывать, может ли переменная быть null, и предупреждают о потенциальных проблемах на этапе компиляции.
2. Неправильное использование var и потеря читаемости
Ключевое слово var позволяет компилятору самому определить тип переменной. Это удобно, но новички часто злоупотребляют им, что делает код нечитаемым.
Пример плохого кода
var data = GetData();
var result = Process(data);
var output = Transform(result); // Что за типы? Непонятно.
Через месяц вы (или ваш коллега) не сможете быстро понять, какой тип у data, не заглядывая в определение метода.
Когда использовать var
- Когда тип очевиден из правой части (например,
var list = new List<int>();) - При работе с анонимными типами и LINQ-запросами.
// Хорошо: тип очевиден
var numbers = new List<int> { 1, 2, 3 };
// Хорошо: анонимный тип
var person = new { Name = "John", Age = 30 };
// Плохо: тип неясен
var config = LoadConfiguration(); // Лучше: Configuration config = LoadConfiguration();
Золотое правило: используйте var только тогда, когда это повышает читаемость, а не снижает её.
3. Проглатывание исключений и пустые catch-блоки
Многие начинающие, сталкиваясь с исключениями, пишут пустой catch или просто выводят сообщение, не анализируя ошибку. Это делает программу нестабильной и затрудняет отладку.
Пример антипаттерна
try
{
int.Parse("не число");
}
catch (Exception)
{
// Ничего не делаем — ошибка "проглочена"
}
Приложение продолжит работу, но в неверном состоянии. Пользователь не узнает о проблеме, а данные могут быть повреждены.
Как обрабатывать исключения правильно
- Не ловите общие
Exception, если не планируете обрабатывать все типы ошибок. - Ловите только те исключения, которые вы можете обработать (например,
FormatException,FileNotFoundException). - Используйте
finallyдля освобождения ресурсов (файлы, соединения). - Логируйте ошибки.
try
{
int number = int.Parse(userInput);
}
catch (FormatException ex)
{
// Логируем и сообщаем пользователю
Console.WriteLine($"Ошибка формата: {ex.Message}");
// Можно предложить ввести данные заново
}
catch (OverflowException ex)
{
Console.WriteLine($"Число слишком большое: {ex.Message}");
}
finally
{
// Закрываем ресурсы, если нужно
}
Если вы не можете обработать исключение — не ловите его. Пусть оно "всплывёт" выше, где его обработают корректно.
4. Ошибки при работе с async/await
Асинхронное программирование в C# — мощный инструмент, но новички часто допускают классические ошибки: блокировка асинхронных методов, забытый await и неправильная обработка исключений в асинхронном коде.
Блокировка асинхронного кода
Никогда не используйте .Result или .Wait() для синхронного ожидания асинхронного метода — это приводит к deadlock'ам.
// ПЛОХО: может вызвать deadlock
var data = GetDataAsync().Result;
// ХОРОШО: используйте await
var data = await GetDataAsync();
Забытый await
Если вы не поставите await перед асинхронным методом, он выполнится, но вы не дождётесь результата и не поймаете исключение.
// ПЛОХО: исключение не будет поймано
try
{
SaveDataAsync(); // await забыт!
}
catch (Exception ex)
{
// Сюда мы не попадём
}
// ХОРОШО
try
{
await SaveDataAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Совет
Старайтесь делать асинхронными "до самого верха" (async all the way). Если метод вызывает асинхронную операцию, пусть он сам будет async и возвращает Task.
Заключение: как не повторять эти ошибки
Мы разобрали 4 ключевые ошибки начинающих C# разработчиков:
- NullReferenceException — всегда проверяйте на null и используйте
?.и??. - Злоупотребление var —