НейроАгент

Лучшие практики перехвата нескольких исключений

Освойте обработку исключений в C# с эффективными техниками одновременного перехвата нескольких исключений. Используйте фильтры исключений с ключевым словом 'when' для избежания повторяющегося кода при сохранении типобезопасности и производительности.

Вопрос

Как перехватывать несколько исключений одновременно в программировании?

При обработке исключений в программировании обычно не рекомендуется перехватывать ошибки System.Exception. Вместо этого следует перехватывать только “известные” исключения. Однако такой подход может привести к повторяющемуся коду, как показано в следующем примере:

csharp
try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

Существует ли способ перехватить оба исключения и установить WebId = Guid.Empty только один раз?

Этот пример относительно прост, но рассмотрите более сложные сценарии, где вы изменяете объект несколько раз. Если одна из манипуляций завершается с ошибкой как ожидалось, вы можете захотеть “сбросить” объект. Однако если происходит непредвиденное исключение, вы все равно хотите передать его выше по стеку вызовов.

Каковы лучшие практики для обработки нескольких конкретных исключений без повторяющегося кода в языках программирования, таких как C#?

НейроАгент

Обработка нескольких исключений в C# с помощью фильтров исключений

В C# можно перехватывать несколько исключений одновременно с помощью фильтров исключений, введенных в C# 6.0, которые позволяют обрабатывать разные типы исключений в одном блоке catch и избегать повторяющегося кода. Современный подход использует ключевое слово when для указания условий для разных типов исключений в одном обработчике.

Содержание

Фильтры исключений в C#

Наиболее элегантное решение для вашей проблемы - использование фильтров исключений с ключевым словом when. Это позволяет перехватывать несколько исключений в одном блоке catch, обрабатывая их по-разному:

csharp
try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

Этот подход:

  • Сохраняет безопасность типов, все еще перехватывая конкретные исключения
  • Избегает повторяющегося кода в нескольких блоках catch
  • Делает очевидным, что оба исключения обрабатываются одинаково
  • Сохраняет исходное исключение для целей отладки

Вы также можете добавить более специфичную обработку в том же блоке:

csharp
try
{
    // Множественные операции, которые могут вызывать разные исключения
}
catch (Exception ex) when (ex is FormatException)
{
    WebId = Guid.Empty;
    LogWarning("Обнаружен неверный формат");
}
catch (Exception ex) when (ex is OverflowException)
{
    WebId = Guid.Empty;
    LogWarning("Произошло переполнение");
}
catch (Exception ex) when (ex is NullReferenceException)
{
    // Обработка иначе
    throw new InvalidOperationException("Требуемый параметр отсутствует", ex);
}

Альтернативные подходы

Обработка исключений на основе методов

Для более сложных сценариев рассмотрите извлечение логики обработки исключений в отдельный метод:

csharp
private static Guid SafeParseGuid(string input)
{
    try
    {
        return new Guid(input);
    }
    catch (FormatException) when (string.IsNullOrEmpty(input))
    {
        return Guid.Empty;
    }
    catch (FormatException)
    {
        return Guid.Empty;
    }
    catch (OverflowException)
    {
        return Guid.Empty;
    }
}

// Использование
WebId = SafeParseGuid(queryString["web"]);

Пользовательские типы исключений

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

csharp
public class GuidParseException : Exception
{
    public GuidParseException(Exception innerException) : base("Не удалось разобрать GUID", innerException) { }
}

// В вашем коде
try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException ex) when (string.IsNullOrEmpty(queryString["web"]))
{
    throw new GuidParseException(ex);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

Рассмотрения производительности

Фильтры исключений более производительны, чем традиционные множественные блоки catch, потому что:

  1. Они не включают разворачивание стека исключений для отфильтрованных исключений
  2. Объект исключения создается только один раз
  3. Нет дополнительного накладных расходов на обработку исключений

Важно: Порядок проверки исключений имеет значение. Более специфичные исключения должны проверяться перед общими, чтобы избежать их маскировки.

csharp
// Правильный порядок - более специфичные сначала
catch (Exception ex) when (ex is FormatException)
{
    // Обработка FormatException
}
catch (Exception ex) when (ex is OverflowException)
{
    // Обработка OverflowException
}
catch (Exception ex) when (ex is ArgumentException)
{
    // Общая обработка аргументов
}

Лучшие практики

1. Будьте конкретны в исключениях

Всегда перехватывайте конкретные исключения, а не общие:

csharp
// Хорошо - конкретные исключения
catch (FormatException)
catch (OverflowException)

// Плохо - слишком общие
catch (Exception)

2. Сохраняйте информацию об исключениях

При обработке исключений сохраняйте важную информацию:

csharp
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    // Логируем исходное исключение
    Logger.Error($"Не удалось разобрать GUID: {ex.Message}");
    
    WebId = Guid.Empty;
}

3. Используйте условную логику внутри фильтров

В фильтры исключений можно применять сложную условную логику:

csharp
catch (Exception ex) when (
    ex is FormatException && IsCriticalFormat(ex) ||
    ex is OverflowException && ShouldHandleOverflow(ex)
)
{
    WebId = Guid.Empty;
}

4. Учитывайте шаблоны Async/Await

Для асинхронных операций обеспечьте правильную обработку исключений:

csharp
try
{
    WebId = await Task.Run(() => new Guid(queryString["web"]));
}
catch (Exception ex) when (ex is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

Сценарии сброса сложных объектов

Для сценариев манипулирования сложными объектами реализуйте шаблон, который позволяет выполнять атомарные операции:

csharp
public class ComplexObject
{
    public void Reset()
    {
        // Сброс всех свойств в безопасные значения по умолчанию
        this.Property1 = default;
        this.Property2 = default;
        // ... другая логика сброса
    }

    public void SafeExecute(Action operation)
    {
        var backup = this.Clone(); // Создаем резервную копию
        
        try
        {
            operation();
        }
        catch (Exception ex) when (IsExpectedException(ex))
        {
            this.Reset(); // Сбрасываем в безопасное состояние
            throw; // Перебрасываем ожидаемые исключения
        }
        catch
        {
            this.Reset(); // Сбрасываем в безопасное состояние
            throw; // Перебрасываем неожиданные исключения
        }
    }

    private bool IsExpectedException(Exception ex)
    {
        return ex is FormatException || 
               ex is OverflowException || 
               ex is ArgumentException;
    }
}

// Использование
var obj = new ComplexObject();
obj.SafeExecute(() => 
{
    // Множественные операции, которые могут вызывать ожидаемые исключения
    obj.Property1 = ParseValue(input1);
    obj.Property2 = ParseValue(input2);
});

Специфичные для языка шаблоны

Сходства Java и C#

Java также поддерживает перехват нескольких исключений:

java
try {
    WebId = UUID.fromString(queryString.get("web"));
} catch (IllegalArgumentException | NumberFormatException ex) {
    WebId = UUID.fromString("00000000-0000-0000-0000-000000000000");
}

Подход Python

Python использует несколько блоков except:

python
try:
    web_id = uuid.UUID(query_string['web'])
except (ValueError, OverflowError):
    web_id = uuid.UUID('{00000000-0000-0000-0000-000000000000}')

JavaScript/TypeScript

JavaScript использует try-catch с проверкой типа:

typescript
try {
    const webId = new UUID(queryString.web);
} catch (ex) {
    if (ex instanceof FormatError || ex instanceof OverflowError) {
        webId = new UUID('00000000-0000-0000-0000-000000000000');
    }
    throw ex; // Перебрасываем неожиданные исключения
}

Заключение

Обработка нескольких исключений в C# может быть элегантно выполнена с помощью фильтров исключений с ключевым словом when, что обеспечивает чистое решение для избегания повторяющихся блоков catch при сохранении безопасности типов и производительности. Ключевые выводы:

  1. Используйте фильтры исключений (ключевое слово when) для обработки нескольких исключений с похожей логикой
  2. Сохраняйте информацию об исключениях даже при обработке нескольких случаев
  3. Учитывайте производительность - фильтры исключений более эффективны, чем несколько блоков catch
  4. Реализуйте атомарные операции для сценариев манипулирования сложными объектами
  5. Следуйте специфичным для языка шаблонам, сохраняя при этом последовательность

Для вашего конкретного примера с разбором GUID подход с фильтрами исключений обеспечивает наиболее лаконичное и поддерживаемое решение, гарантируя корректную обработку как FormatException, так и OverflowException.

Источники

  1. Справочник по языку C# Microsoft - Фильтры исключений
  2. Возможности C# 6.0 - Фильтры исключений
  3. Лучшие практики обработки исключений
  4. Рассмотрения производительности в обработке исключений