Ответ
Да, создание собственных классов исключений — это лучшая практика для моделирования специфичных для домена ошибок приложения. Это позволяет четко разделять логику обработки сбоев.
Зачем создавать свои исключения?
- Семантика: Исключение
InvalidOrderStateExceptionпонятнее, чем общееInvalidOperationException. - Контекст: Можно добавить дополнительные свойства, несущие информацию об ошибке (например,
OrderId,CurrentState). - Фильтрация: Обработчики могут ловить конкретные типы исключений, а не все подряд.
Как правильно создать пользовательское исключение:
- Наследование: Класс должен наследоваться от
System.Exceptionили более конкретного исключения (например,ArgumentException). - Конструкторы: Реализовать три стандартных конструктора для совместимости с системой сериализации.
- Соглашение об именах: Имя класса должно заканчиваться суффиксом
Exception. - Сериализация (для cross-domain): Пометить класс атрибутом
[Serializable]и реализовать защищенный конструктор для десериализации.
Пример:
[Serializable] // Для поддержки сериализации через границы доменов приложения
public class InsufficientFundsException : InvalidOperationException
{
// Дополнительные свойства для контекста ошибки
public string AccountNumber { get; }
public decimal CurrentBalance { get; }
public decimal RequiredAmount { get; }
// Стандартные конструкторы
public InsufficientFundsException() { }
public InsufficientFundsException(string message) : base(message) { }
public InsufficientFundsException(string message, Exception innerException)
: base(message, innerException) { }
// Конструктор с дополнительными данными
public InsufficientFundsException(string accountNumber, decimal currentBalance, decimal requiredAmount)
: base($"Account '{accountNumber}' has insufficient funds. Current: {currentBalance}, Required: {requiredAmount}")
{
AccountNumber = accountNumber;
CurrentBalance = currentBalance;
RequiredAmount = requiredAmount;
}
// Конструктор для десериализации (важен для cross-appdomain)
protected InsufficientFundsException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
AccountNumber = info.GetString(nameof(AccountNumber));
CurrentBalance = info.GetDecimal(nameof(CurrentBalance));
RequiredAmount = info.GetDecimal(nameof(RequiredAmount));
}
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(RequiredAmount), RequiredAmount);
}
}
// Использование
public void ProcessWithdrawal(string accountId, decimal amount)
{
var account = GetAccount(accountId);
if (account.Balance < amount)
{
throw new InsufficientFundsException(account.Id, account.Balance, amount);
}
// ... логика списания
}
Главное правило: Не создавайте новые исключения, если в BCL уже существует подходящее по смыслу (например, ArgumentNullException, InvalidOperationException). Свои исключения нужны для ошибок, уникальных для бизнес-логики вашего приложения.