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

Как написать свое исключение?

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

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

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

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

Как написать пользовательское исключение в C#

Создание собственных исключений в C# — это важная практика для построения качественного, сопровождаемого и информативного кода. Пользовательские исключения позволяют точно отражать специфические ошибки предметной области вашего приложения, что упрощает отладку, обработку ошибок и делает код более читаемым.

Основные принципы создания пользовательских исключений

1. Наследование от соответствующего базового класса Все пользовательские исключения должны наследоваться от класса System.Exception или его производных. Рекомендуется использовать следующую иерархию:

  • Для восстанавливаемых ошибок бизнес-логики наследуйтесь от ApplicationException (хотя эта практика несколько устарела)
  • Для критических ошибок системы наследуйтесь от SystemException
  • Для конкретных типов ошибок используйте наиболее подходящий базовый класс: ArgumentException, InvalidOperationException и т.д.

2. Реализация четырех стандартных конструкторов Для совместимости с общепринятыми практиками .NET рекомендуется реализовать все четыре стандартных конструктора:

public class CustomException : Exception
{
    // Конструктор по умолчанию
    public CustomException() 
        : base() 
    { }
    
    // Конструктор с сообщением об ошибке
    public CustomException(string message) 
        : base(message) 
    { }
    
    // Конструктор с сообщением и внутренним исключением
    public CustomException(string message, Exception innerException) 
        : base(message, innerException) 
    { }
    
    // Конструктор для сериализации (важно для кросс-доменных сценариев)
    protected CustomException(SerializationInfo info, StreamingContext context) 
        : base(info, context) 
    { }
}

Практический пример: исключение для банковского приложения

Рассмотрим пример исключения для банковской системы, которое генерируется при недостатке средств на счете:

using System;
using System.Runtime.Serialization;

namespace BankingApplication.Exceptions
{
    /// <summary>
    /// Исключение, возникающее при попытке списать сумму, превышающую баланс счета
    /// </summary>
    [Serializable]
    public class InsufficientFundsException : InvalidOperationException
    {
        // Дополнительные свойства для хранения контекстной информации
        public string AccountNumber { get; }
        public decimal CurrentBalance { get; }
        public decimal RequestedAmount { get; }
        
        // Вычисляемое свойство для недостающей суммы
        public decimal Deficit => RequestedAmount - CurrentBalance;
        
        // Конструкторы с дополнительными параметрами
        public InsufficientFundsException(string accountNumber, decimal currentBalance, decimal requestedAmount)
            : base($"Недостаточно средств на счете {accountNumber}. " +
                   $"Текущий баланс: {currentBalance:C}, запрошено: {requestedAmount:C}")
        {
            AccountNumber = accountNumber;
            CurrentBalance = currentBalance;
            RequestedAmount = requestedAmount;
        }
        
        public InsufficientFundsException(string accountNumber, decimal currentBalance, 
                                         decimal requestedAmount, Exception innerException)
            : base($"Недостаточно средств на счете {accountNumber}. " +
                   $"Текущий баланс: {currentBalance:C}, запрошено: {requestedAmount:C}", 
                   innerException)
        {
            AccountNumber = accountNumber;
            CurrentBalance = currentBalance;
            RequestedAmount = requestedAmount;
        }
        
        // Конструктор для сериализации
        protected InsufficientFundsException(SerializationInfo info, StreamingContext context) 
            : base(info, context)
        {
            AccountNumber = info.GetString(nameof(AccountNumber));
            CurrentBalance = info.GetDecimal(nameof(CurrentBalance));
            RequestedAmount = info.GetDecimal(nameof(RequestedAmount));
        }
        
        // Переопределение GetObjectData для корректной сериализации
        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue(nameof(AccountNumber), AccountNumber);
            info.AddValue(nameof(CurrentBalance), CurrentBalance);
            info.AddValue(nameof(RequestedAmount), RequestedAmount);
        }
    }
}

Рекомендации и best practices

Ключевые рекомендации при разработке исключений:

  1. Именование: Имена исключений должны заканчиваться на "Exception" (например, ValidationException, DatabaseConnectionException)

  2. Сериализация: Помечайте класс атрибутом [Serializable] и реализуйте соответствующий конструктор, если исключение может пересекать границы доменов приложений или процессов

  3. Дополнительные данные: Добавляйте свойства для хранения контекстной информации, которая поможет в диагностике проблемы

  4. Полезные сообщения: Сообщения об ошибках должны быть информативными, но не раскрывать конфиденциальную информацию или детали реализации

  5. Локализация: Для многоязычных приложений предусмотрите возможность локализации сообщений об ошибках

Пример использования пользовательского исключения

public class BankAccount
{
    private decimal _balance;
    
    public void Withdraw(decimal amount)
    {
        if (amount > _balance)
        {
            throw new InsufficientFundsException(
                accountNumber: "ACC123456",
                currentBalance: _balance,
                requestedAmount: amount
            );
        }
        
        _balance -= amount;
    }
}

// Обработка исключения
try
{
    account.Withdraw(1000m);
}
catch (InsufficientFundsException ex)
{
    // Логирование с полной информацией
    logger.LogWarning(
        "Неудачная попытка снятия средств. Счет: {Account}, " +
        "Недостающая сумма: {Deficit}",
        ex.AccountNumber,
        ex.Deficit
    );
    
    // Пользовательское сообщение
    ShowMessage($"Ошибка: {ex.Message}");
}

Заключение

Создание пользовательских исключений — это не просто техническая необходимость, а важный аспект проектирования качественного API и архитектуры приложения. Правильно спроектированные исключения:

  • Улучшают диагностику проблем в production-среде
  • Упрощают обработку ошибок на различных уровнях приложения
  • Делают код более самодокументируемым
  • Позволяют разделить системные ошибки и ошибки предметной области

Главное правило: создавайте специализированные исключения только тогда, когда стандартные исключения .NET неадекватно описывают возникшую ситуацию. Избыточное создание исключений так же вредно, как и их отсутствие — найдите баланс между детализацией и простотой поддержки.

Как написать свое исключение? | PrepBro