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

Что такое Throw?

1.0 Junior🔥 201 комментариев
#Основы C# и .NET

Комментарии (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+) делает код компактнее