Использование исключений
В C# ошибки в программе в среде выполнения передаются через программу с помощью механизма, который называется исключениями. Исключения вызываются кодом, который встречает ошибку, и перехватываются кодом, который может ее исправить. Исключения могут вызываться средой выполнения .NET или кодом в программе. Вызванное исключение передается вверх по стеку вызовов, пока не будет найден соответствующий оператор catch
. Не перехваченные исключения обрабатываются универсальным обработчиком исключений, предоставляемым системой, которая отображает диалоговое окно.
Исключения представляются классами, производными от Exception. Этот класс определяет тип исключения и содержит свойства с подробными сведениями об исключении. При вызове исключения создается экземпляр производного класса, а также могут настраиваться свойства исключения. После этого с помощью ключевого слова throw
вызывается объект. Например:
class CustomException : Exception
{
public CustomException(string message)
{
}
}
private static void TestThrow()
{
throw new CustomException("Custom exception in TestThrow()");
}
После выдачи исключения среда выполнения проверяет, входит ли текущий оператор в блок try
. Если да, она проверяет, может ли какой-либо из блоков catch
, связанных с блоком try
, перехватить исключение. Блоки Catch
обычно задают типы исключений; если тип блока catch
совпадает с типом или базовым классом исключения, блок catch
может обработать этот метод. Например:
try
{
TestThrow();
}
catch (CustomException ex)
{
System.Console.WriteLine(ex.ToString());
}
Если оператор, который вызывает исключение, не находится в блоке try
, или блок try
, в который он входит, не имеет соответствующий блок catch
, среда выполнения проверяет вызывающий метод на наличие оператора try
и блоков catch
. Среда выполнения продолжает перебирать стек вызовов в поиска подходящего блока catch
. После того как блок catch
будет найден и выполнен, управление передается оператору, следующему после блока catch
.
Оператор try
может содержать не один блок catch
. Выполняется первый оператор catch
, который может обработать исключение; все последующие операторы catch
игнорируются, даже если они совместимы. Порядок перехвата блокируется от наиболее конкретных (или наиболее производных) до наименее конкретных. Например:
using System;
using System.IO;
namespace Exceptions
{
public class CatchOrder
{
public static void Main()
{
try
{
using (var sw = new StreamWriter("./test.txt"))
{
sw.WriteLine("Hello");
}
}
// Put the more specific exceptions first.
catch (DirectoryNotFoundException ex)
{
Console.WriteLine(ex);
}
catch (FileNotFoundException ex)
{
Console.WriteLine(ex);
}
// Put the least specific exception last.
catch (IOException ex)
{
Console.WriteLine(ex);
}
Console.WriteLine("Done");
}
}
}
Прежде чем выполнять блок catch
, среда выполнения проверяет наличие блоков finally
. Блоки Finally
позволяют программисту удалить любое неоднозначное состояние, которое может остаться после прерванного блока try
, а также освободить любые внешние ресурсы (включая обработчики графики, соединители баз данных или файловые потоки), не дожидаясь, пока сборщик мусора в среде выполнения завершит эти объекты. Например:
static void TestFinally()
{
FileStream? file = null;
//Change the path to something that works on your machine.
FileInfo fileInfo = new System.IO.FileInfo("./file.txt");
try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
file?.Close();
}
try
{
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
}
catch (IOException)
{
Console.WriteLine("OpenWrite() failed");
}
}
Если WriteByte()
вызывает исключение, то код во втором блоке try
, который пытается повторно открыть файл, завершится ошибкой, если file.Close()
не вызывается, а файл остается заблокированным. Поскольку блоки finally
выполняются, даже если выдается исключение, блок finally
в предыдущем примере обеспечивает правильное закрытие файла и помогает избежать ошибки.
Если после выдачи исключения обнаружить совместимый блок catch
в стеке вызовов не удается, происходит одно из трех:
- Если исключение находится в методе завершения, метод завершения прерван, а базовый метод завершения, если таковой есть, вызывается.
- Если стек вызовов содержит статический конструктор или инициализатор статического поля, выдается исключение TypeInitializationException, а исходное исключение назначается свойству InnerException нового исключения.
- Как только достигается начало потока, он прерывается.