Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Throw в C# — Бросание исключений
Throw — это оператор, который выбрасывает исключение и прерывает нормальный ход выполнения программы. За 10+ лет разработки я убедился, что правильная работа с throw — это признак опытного разработчика. Неправильное использование throw может привести к утечкам памяти, потере данных и неуловимым ошибкам.
Определение
Throw — это оператор, который создаёт объект Exception и передаёт управление в блок catch (если он есть) или прерывает приложение (если блока catch нет).
Базовый синтаксис
// Выбрасываем новое исключение
throw new ArgumentNullException(nameof(parameter));
// Или переиспользуем существующее
try
{
ProcessData();
}
catch (InvalidOperationException ex)
{
// Пробрасываем дальше
throw;
}
Типы исключений
В .NET есть иерархия исключений:
Exception
├── SystemException
│ ├── ArgumentException
│ ├── ArgumentNullException
│ ├── InvalidOperationException
│ ├── NullReferenceException
│ └── ...
└── ApplicationException (для пользовательских)
Распространённые исключения
// NullReferenceException — используется объект null
string name = null;
console.WriteLine(name.Length); // ❌ throws NullReferenceException
// ArgumentNullException — параметр null
public void ProcessUser(User user)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
}
// ArgumentException — неправильное значение параметра
public void SetAge(int age)
{
if (age < 0 || age > 150)
throw new ArgumentException($"Age must be between 0 and 150", nameof(age));
}
// InvalidOperationException — неправильное состояние объекта
if (!_isInitialized)
throw new InvalidOperationException("Object is not initialized");
// FormatException — ошибка парсинга
try
{
int.Parse("not a number");
}
catch (FormatException)
{
throw new FormatException("Invalid number format");
}
Обработка исключений: try-catch-finally
try
{
// Код, который может вызвать исключение
var user = await _userRepository.GetUserAsync(userId);
user.Email = newEmail;
await _userRepository.SaveAsync(user);
}
catch (NotFoundException ex)
{
// Обработка конкретного исключения
_logger.LogWarning($"User not found: {ex.Message}");
return NotFound();
}
catch (DbException ex)
{
// Обработка другого типа
_logger.LogError($"Database error: {ex}");
throw; // Пробрасываем дальше
}
finally
{
// Выполняется ВСЕГДА (очистка ресурсов)
_connection?.Dispose();
}
Правильное бросание исключений
1. Используй правильный тип исключения
// ❌ НЕПРАВИЛЬНО — используешь Exception
public void ValidateEmail(string email)
{
if (!email.Contains("@"))
throw new Exception("Invalid email");
}
// ✅ ПРАВИЛЬНО — используешь ArgumentException
public void ValidateEmail(string email)
{
if (!email.Contains("@"))
throw new ArgumentException("Email must contain @", nameof(email));
}
2. Включай контекст в сообщение
// ❌ НЕПРАВИЛЬНО — неинформативно
throw new Exception("Error");
// ✅ ПРАВИЛЬНО — ясное объяснение
throw new InvalidOperationException(
$"Cannot delete user with ID {userId} because they have {activeOrders} active orders"
);
3. Используй InnerException для цепочки исключений
try
{
var data = await _externalApi.FetchDataAsync();
}
catch (HttpRequestException ex) // Исключение от API
{
// Оборачиваем в более высокоуровневое исключение
throw new DataFetchException("Failed to fetch data from external API", ex);
}
4. Пробрасывай, не теряя стек вызовов
// ❌ НЕПРАВИЛЬНО — теряешь оригинальный стек
catch (Exception ex)
{
throw new CustomException($"Error: {ex.Message}"); // Стек потерян
}
// ✅ ПРАВИЛЬНО — сохраняешь оригинальный стек
catch (Exception ex)
{
throw new CustomException($"Error: {ex.Message}", ex); // Стек сохранён
}
// ✅ ЛУЧШЕ — просто пробрасываешь дальше
catch (SomeSpecificException)
{
throw; // Сохраняет оригинальный стек и контекст
}
Создание пользовательского исключения
// Наследуемся от Exception или более специфичного исключения
public class UserNotFoundException : Exception
{
public int UserId { get; }
public UserNotFoundException(int userId)
: base($"User with ID {userId} not found")
{
UserId = userId;
}
public UserNotFoundException(int userId, Exception innerException)
: base($"User with ID {userId} not found", innerException)
{
UserId = userId;
}
}
// Использование
try
{
var user = _users.FirstOrDefault(u => u.Id == userId);
if (user == null)
throw new UserNotFoundException(userId);
}
catch (UserNotFoundException ex)
{
_logger.LogWarning($"User lookup failed: {ex.UserId}");
}
Throw Expression (C# 7.0+)
// Традиционный способ
var name = user?.Name;
if (name == null)
throw new ArgumentNullException(nameof(user));
// ✅ Throw expression — компактнее
var name = user?.Name ?? throw new ArgumentNullException(nameof(user));
// Или в методе
public void ProcessUser(User user)
=> user ?? throw new ArgumentNullException(nameof(user));
Лучшие практики
1. Выбрасывай исключения, а не возвращай null
// ❌ НЕПРАВИЛЬНО
public User GetUser(int id)
{
return _repository.FirstOrDefault(u => u.Id == id); // Возвращает null?
}
// ✅ ПРАВИЛЬНО
public User GetUser(int id)
{
var user = _repository.FirstOrDefault(u => u.Id == id);
return user ?? throw new UserNotFoundException(id);
}
2. Выбрасывай как можно раньше (fail fast)
// ❌ НЕПРАВИЛЬНО — проверка в конце
public void TransferMoney(User from, User to, decimal amount)
{
from.Balance -= amount;
to.Balance += amount;
if (from.Balance < 0) // Проверка слишком поздно!
throw new InvalidOperationException();
}
// ✅ ПРАВИЛЬНО — проверка в начале
public void TransferMoney(User from, User to, decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Amount must be positive", nameof(amount));
if (from.Balance < amount)
throw new InvalidOperationException("Insufficient funds");
if (to == null)
throw new ArgumentNullException(nameof(to));
// Теперь безопасно
from.Balance -= amount;
to.Balance += amount;
}
3. Логируй исключения на правильном уровне
try
{
ProcessRequest();
}
catch (ValidationException ex)
{
// Ожидаемая ошибка — логируем как Warning
_logger.LogWarning($"Validation failed: {ex.Message}");
throw;
}
catch (Exception ex)
{
// Неожиданная ошибка — логируем как Error
_logger.LogError(ex, "Unexpected error occurred");
throw;
}
Выводы для интервью
- Throw выбрасывает исключение и прерывает нормальное выполнение
- Используй правильные типы исключений (ArgumentNullException, InvalidOperationException и т.д.)
- Включай контекст в сообщение об ошибке
- Сохраняй цепочку исключений через InnerException
- Выбрасывай исключения как можно раньше (fail fast)
- Создавай пользовательские исключения для бизнес-ошибок
- Throw expression (C# 7.0+) делает код компактнее