Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Операторы обработки исключений —
Для работы с исключениями используются throw
инструкции и try
инструкции. Используйте инструкцию throw
для создания исключения. Используйте инструкцию try
для перехвата и обработки исключений, которые могут возникать во время выполнения блока кода.
Инструкция throw
Оператор throw
создает исключение:
if (shapeAmount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}
В операторе throw e;
результат выражения e
должен быть неявно преобразован в System.Exception.
Можно использовать встроенные классы исключений, например ArgumentOutOfRangeException или InvalidOperationException. .NET также предоставляет следующие вспомогательные методы для создания исключений в определенных условиях: ArgumentNullException.ThrowIfNull и ArgumentException.ThrowIfNullOrEmpty. Вы также можете определить собственные классы исключений, производные от System.Exception. Дополнительные сведения см. в разделе "Создание и создание исключений".
catch
В блоке можно использовать инструкцию throw;
для повторного создания исключения, которое обрабатывается блокомcatch
:
try
{
ProcessShapes(shapeAmount);
}
catch (Exception e)
{
LogError(e, "Shape processing failed.");
throw;
}
Примечание.
throw;
сохраняет исходную трассировку стека исключения, которая хранится в свойстве Exception.StackTrace . Напротив этого throw e;
обновляется StackTrace свойство e
.
При возникновении исключения среда CLR ищет catch
блок , который может обрабатывать это исключение. Если текущий выполняемый метод не содержит такой catch
блок, среда CLR смотрит на метод, который вызывает текущий метод, и т. д. в стеке вызовов. Если блок не catch
найден, среда CLR завершает исполняемый поток. Дополнительные сведения см. в разделе "Обработка исключений" спецификации языка C#.
Выражение throw
Вы также можете использовать throw
в качестве выражения. Это может быть удобно в ряде случаев, в том числе:
Условный оператор. В следующем примере используется выражение для создания
throw
, когда переданный ArgumentException массивargs
пуст:string first = args.Length >= 1 ? args[0] : throw new ArgumentException("Please supply at least one argument.");
Оператор объединения с NULL. В следующем примере используется
throw
выражение для создания ArgumentNullException строки для назначения свойствуnull
:public string Name { get => name; set => name = value ?? throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null"); }
Метод или лямбда, воплощающие выражение. В следующем примере используется
throw
выражение для создания выражения InvalidCastException , указывающего, что преобразование в DateTime значение не поддерживается:DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException("Conversion to a DateTime is not supported.");
Инструкция try
Инструкцию try
можно использовать в любой из следующих форм: try-catch
для обработки исключений, которые могут возникать во время выполнения кода внутри try
блока, try-finally
— для указания кода, выполняемого при выходе try
элемента управления из блока, и try-catch-finally
в сочетании с предыдущими двумя формами.
Инструкция try-catch
Используйте инструкцию try-catch
для обработки исключений, которые могут возникать во время выполнения блока кода. Поместите код, в котором может возникнуть исключение в блоке try
. Используйте предложение catch, чтобы указать базовый тип исключений, которые необходимо обрабатывать в соответствующем catch
блоке:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
Можно указать несколько предложений catch:
try
{
var result = await ProcessAsync(-3, 4, cancellationToken);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
catch (OperationCanceledException)
{
Console.WriteLine("Processing is cancelled.");
}
При возникновении исключения предложения catch проверяются в указанном порядке с верхней до нижней части. Не более одного catch
блока выполняется только для любого вызываемого исключения. Как показано в предыдущем примере, можно опустить объявление переменной исключения и указать только тип исключения в предложении catch. Предложение catch без какого-либо указанного типа исключения соответствует любому исключению и, если оно присутствует, должно быть последним предложением catch.
Если вы хотите повторно создать исключение, используйте инструкциюthrow
, как показано в следующем примере:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
LogError(e, "Processing failed.");
throw;
}
Примечание.
throw;
сохраняет исходную трассировку стека исключения, которая хранится в свойстве Exception.StackTrace . Напротив этого throw e;
обновляется StackTrace свойство e
.
Фильтр исключений when
Наряду с типом исключения можно также указать фильтр исключений, который дополнительно проверяет исключение и решает, обрабатывает ли соответствующее catch
исключение блок. Фильтр исключений — это логическое выражение, которое следует when
ключевому слову, как показано в следующем примере:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e) when (e is ArgumentException || e is DivideByZeroException)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
В предыдущем примере фильтр исключений используется для предоставления одного catch
блока для обработки исключений двух указанных типов.
Можно указать несколько catch
предложений для одного типа исключения, если они различаются фильтрами исключений. Одно из этих предложений может не иметь фильтра исключений. Если такое предложение существует, оно должно быть последним из предложений, которые указывают этот тип исключения.
catch
Если предложение имеет фильтр исключений, он может указать тип исключения, который совпадает с или менее производным, чем тип catch
исключения предложения, который отображается после него. Например, если фильтр исключений присутствует, catch (Exception e)
предложение не должно быть последним предложением.
Фильтры исключений и традиционная обработка исключений
Фильтры исключений обеспечивают значительные преимущества по сравнению с традиционными подходами к обработке исключений. Ключевое различие заключается в оценке логики обработки исключений:
-
Фильтры исключений (
when
): выражение фильтра вычисляется до отмены стека. Это означает, что исходный стек вызовов и все локальные переменные остаются неизменными во время оценки фильтра. -
Традиционные
catch
блоки: блок catch выполняется после отмены стека, потенциально теряя ценные сведения об отладке.
Ниже приведено сравнение, показывающее разницу:
public static void DemonstrateStackUnwindingDifference()
{
var localVariable = "Important debugging info";
try
{
ProcessWithExceptionFilter(localVariable);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("filter"))
{
// Exception filter: Stack not unwound yet.
// localVariable is still accessible in debugger.
// Call stack shows original throwing location.
Console.WriteLine($"Caught with filter: {ex.Message}");
Console.WriteLine($"Local variable accessible: {localVariable}");
}
try
{
ProcessWithTraditionalCatch(localVariable);
}
catch (InvalidOperationException ex)
{
// Traditional catch: Stack already unwound.
// Some debugging information may be lost.
if (ex.Message.Contains("traditional"))
{
Console.WriteLine($"Caught with if: {ex.Message}");
Console.WriteLine($"Local variable accessible: {localVariable}");
}
else
{
throw; // Re-throws and further modifies stack trace.
}
}
}
private static void ProcessWithExceptionFilter(string context)
{
throw new InvalidOperationException($"Exception for filter demo: {context}");
}
private static void ProcessWithTraditionalCatch(string context)
{
throw new InvalidOperationException($"Exception for traditional demo: {context}");
}
Преимущества фильтров исключений
- Улучшенная отладка. Так как стек не будет расправлен до тех пор, пока фильтр не соответствует, отладчики могут показать исходную точку сбоя со всеми локальными переменными без изменений.
- Преимущества производительности. Если фильтр не соответствует, исключение продолжает распространяться без дополнительных расходов на очистку и восстановление стека.
- Более чистый код: несколько фильтров могут обрабатывать различные условия одного типа исключения, не требуя вложенных операторов if-else.
- Ведение журнала и диагностика. Перед решением о том, следует ли обрабатывать исключение, можно изучить и просмотреть сведения об исключении.
public static void DemonstrateDebuggingAdvantage()
{
var contextData = new Dictionary<string, object>
{
["RequestId"] = Guid.NewGuid(),
["UserId"] = "user123",
["Timestamp"] = DateTime.Now
};
try
{
// Simulate a deep call stack.
Level1Method(contextData);
}
catch (Exception ex) when (LogAndFilter(ex, contextData))
{
// This catch block may never execute if LogAndFilter returns false.
// But LogAndFilter can examine the exception while the stack is intact.
Console.WriteLine("Exception handled after logging");
}
}
private static void Level1Method(Dictionary<string, object> context)
{
Level2Method(context);
}
private static void Level2Method(Dictionary<string, object> context)
{
Level3Method(context);
}
private static void Level3Method(Dictionary<string, object> context)
{
throw new InvalidOperationException("Error in deep call stack");
}
private static bool LogAndFilter(Exception ex, Dictionary<string, object> context)
{
// This method runs before stack unwinding.
// Full call stack and local variables are still available.
Console.WriteLine($"Exception occurred: {ex.Message}");
Console.WriteLine($"Request ID: {context["RequestId"]}");
Console.WriteLine($"Full stack trace preserved: {ex.StackTrace}");
// Return true to handle the exception, false to continue search.
return ex.Message.Contains("deep call stack");
}
Когда следует использовать фильтры исключений
При необходимости используйте фильтры исключений:
- Обработка исключений на основе определенных условий или свойств.
- Сохраните исходный стек вызовов для отладки.
- Журнал или проверка исключений перед решением о том, следует ли обрабатывать их.
- Обрабатывать один и тот же тип исключения по-разному в зависимости от контекста.
public static void HandleFileOperations(string filePath)
{
try
{
// Simulate file operation that might fail.
ProcessFile(filePath);
}
catch (IOException ex) when (ex.Message.Contains("access denied"))
{
Console.WriteLine("File access denied. Check permissions.");
}
catch (IOException ex) when (ex.Message.Contains("not found"))
{
Console.WriteLine("File not found. Verify the path.");
}
catch (IOException ex) when (IsNetworkPath(filePath))
{
Console.WriteLine($"Network file operation failed: {ex.Message}");
}
catch (IOException)
{
Console.WriteLine("Other I/O error occurred.");
}
}
private static void ProcessFile(string filePath)
{
// Simulate different types of file exceptions.
if (filePath.Contains("denied"))
throw new IOException("File access denied");
if (filePath.Contains("missing"))
throw new IOException("File not found");
if (IsNetworkPath(filePath))
throw new IOException("Network timeout occurred");
}
private static bool IsNetworkPath(string path)
{
return path.StartsWith(@"\\") || path.StartsWith("http");
}
Сохранение трассировки стека
Фильтры исключений сохраняют исходное ex.StackTrace
свойство.
catch
Если предложение не может обработать исключение и повторно создает исключение, исходные сведения стека будут потеряны. Фильтр when
не отсоединяет стек, поэтому если when
фильтр является false
, исходная трассировка стека не изменяется.
Подход к фильтру исключений ценен в приложениях, где сохранение сведений об отладке имеет решающее значение для диагностики проблем.
Исключения в асинхронных и итераторах
Если исключение возникает в асинхронной функции, он распространяется на вызывающий объект функции при ожидании результата функции, как показано в следующем примере:
public static async Task Run()
{
try
{
Task<int> processing = ProcessAsync(-1);
Console.WriteLine("Launched processing.");
int result = await processing;
Console.WriteLine($"Result: {result}.");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
// Output:
// Launched processing.
// Processing failed: Input must be non-negative. (Parameter 'input')
}
private static async Task<int> ProcessAsync(int input)
{
if (input < 0)
{
throw new ArgumentOutOfRangeException(nameof(input), "Input must be non-negative.");
}
await Task.Delay(500);
return input;
}
Если исключение возникает в методе итератора, оно распространяется на вызывающий объект, только если итератор переходит к следующему элементу.
Инструкция try-finally
В инструкции try-finally
finally
блок выполняется при выходе try
элемента управления из блока. Элемент управления может оставить try
блок в результате
- нормальное выполнение,
- выполнение инструкции перехода (т. е. ,
return
break
,continue
или ) илиgoto
- распространение исключения из
try
блока.
В следующем примере блок используется finally
для сброса состояния объекта перед выходом элемента управления из метода:
public async Task HandleRequest(int itemId, CancellationToken ct)
{
Busy = true;
try
{
await ProcessAsync(itemId, ct);
}
finally
{
Busy = false;
}
}
Вы также можете использовать finally
блок для очистки выделенных ресурсов, используемых в блоке try
.
Примечание.
Если тип ресурса реализует IDisposable или интерфейс, рассмотрите инструкциюIAsyncDisposableusing
. Инструкция using
гарантирует, что приобретенные ресурсы удаляются при выходе элемента using
управления из оператора. Компилятор преобразует using
оператор в try-finally
оператор.
finally
Выполнение блока зависит от того, выбирает ли операционная система активацию операции очистки исключения. Единственные случаи, когда finally
блоки не выполняются, включают немедленное завершение программы. Например, такое завершение может произойти из-за Environment.FailFast вызова или OverflowExceptionInvalidProgramException исключения. Большинство операционных систем выполняют разумное очистку ресурсов в рамках остановки и выгрузки процесса.
Инструкция try-catch-finally
Оператор используется try-catch-finally
как для обработки исключений, которые могут возникать во время выполнения try
блока, так и для указания кода, который должен выполняться при выходе try
элемента управления из оператора:
public async Task ProcessRequest(int itemId, CancellationToken ct)
{
Busy = true;
try
{
await ProcessAsync(itemId, ct);
}
catch (Exception e) when (e is not OperationCanceledException)
{
LogError(e, $"Failed to process request for item ID {itemId}.");
throw;
}
finally
{
Busy = false;
}
}
Если исключение обрабатывается блоком catch
, finally
блок выполняется после выполнения этого catch
блока (даже если во время выполнения catch
блока возникает другое исключение). Сведения о инструкциях и catch
блоках см. в разделах finally
и try-catch
соответственно.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#: