Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как написать пользовательское исключение в 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
Ключевые рекомендации при разработке исключений:
-
Именование: Имена исключений должны заканчиваться на "Exception" (например,
ValidationException,DatabaseConnectionException) -
Сериализация: Помечайте класс атрибутом
[Serializable]и реализуйте соответствующий конструктор, если исключение может пересекать границы доменов приложений или процессов -
Дополнительные данные: Добавляйте свойства для хранения контекстной информации, которая поможет в диагностике проблемы
-
Полезные сообщения: Сообщения об ошибках должны быть информативными, но не раскрывать конфиденциальную информацию или детали реализации
-
Локализация: Для многоязычных приложений предусмотрите возможность локализации сообщений об ошибках
Пример использования пользовательского исключения
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 неадекватно описывают возникшую ситуацию. Избыточное создание исключений так же вредно, как и их отсутствие — найдите баланс между детализацией и простотой поддержки.