Поскольку я чувствовал, что эти ответы только коснулись поверхности, я попытался копнуть немного глубже.
Итак, что мы действительно хотим сделать, это то, что не компилируется, скажем:
// Won't compile... damn
public static void Main()
{
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException)
catch (IndexOutOfRangeException)
{
// ... handle
}
Причина, по которой мы хотим этого, заключается в том, что мы не хотим, чтобы обработчик исключений перехватывал вещи, которые нам понадобятся позже в процессе. Конечно, мы можем поймать исключение и проверить, что делать, если «да», но давайте будем честными, мы этого не хотим. (FxCop, проблемы с отладчиком, уродство)
Так почему этот код не скомпилируется - и как мы можем взломать его таким образом, что он будет?
Если мы посмотрим на код, то, что мы действительно хотели бы сделать, это переадресовать вызов. Однако, согласно MS Partition II, блоки обработчиков исключений IL не будут работать таким образом, что в этом случае имеет смысл, поскольку это подразумевает, что объект «исключения» может иметь разные типы.
Или, чтобы записать это в коде, мы просим компилятор сделать что-то вроде этого (ну, это не совсем правильно, но я думаю, это самая близкая вещь):
// Won't compile... damn
try
{
throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
Console.WriteLine("Handle!");
}
Причина, по которой это не скомпилируется, совершенно очевидна: какой тип и значение будет иметь объект '$ exception' (который здесь хранится в переменных 'e')? Мы хотим, чтобы компилятор справился с этим, отметив, что общим базовым типом обоих исключений является «Исключение», используйте его для переменной, содержащей оба исключения, а затем обрабатывайте только два захваченных исключения. Способ, которым это реализовано в IL, является «фильтром», который доступен в VB.Net.
Чтобы он работал в C #, нам нужна временная переменная с правильным базовым типом «Exception». Чтобы контролировать поток кода, мы можем добавить несколько веток. Здесь идет:
Exception ex;
try
{
throw new ArgumentException(); // for demo purposes; won't be caught.
goto noCatch;
}
catch (ArgumentOutOfRangeException e) {
ex = e;
}
catch (IndexOutOfRangeException e) {
ex = e;
}
Console.WriteLine("Handle the exception 'ex' here :-)");
// throw ex ?
noCatch:
Console.WriteLine("We're done with the exception handling.");
Очевидными недостатками этого являются то, что мы не можем перебросить должным образом, и, давайте будем честными, это довольно уродливое решение. Уродливость можно немного исправить, выполнив удаление ветвей, что делает решение немного лучше:
Exception ex = null;
try
{
throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
ex = e;
}
catch (IndexOutOfRangeException e)
{
ex = e;
}
if (ex != null)
{
Console.WriteLine("Handle the exception here :-)");
}
Это оставляет только «переброс». Чтобы это работало, нам нужно иметь возможность выполнять обработку внутри блока «catch» - и единственный способ выполнить эту работу - перехватывать объект «Exception».
На данный момент мы можем добавить отдельную функцию, которая обрабатывает различные типы исключений, используя разрешение перегрузки, или обрабатывает исключение. Оба имеют недостатки. Для начала вот способ сделать это с помощью вспомогательной функции:
private static bool Handle(Exception e)
{
Console.WriteLine("Handle the exception here :-)");
return true; // false will re-throw;
}
public static void Main()
{
try
{
throw new OutOfMemoryException();
}
catch (ArgumentException e)
{
if (!Handle(e)) { throw; }
}
catch (IndexOutOfRangeException e)
{
if (!Handle(e)) { throw; }
}
Console.WriteLine("We're done with the exception handling.");
И другое решение - перехватить объект Exception и обработать его соответствующим образом. Наиболее буквальный перевод для этого, основанный на контексте выше, это:
try
{
throw new ArgumentException();
}
catch (Exception e)
{
Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
if (ex != null)
{
Console.WriteLine("Handle the exception here :-)");
// throw ?
}
else
{
throw;
}
}
Итак, подытожим:
- Если мы не хотим перебрасывать, мы могли бы рассмотреть возможность перехвата правильных исключений и их временного хранения.
- Если обработчик прост и мы хотим повторно использовать код, лучшим решением, вероятно, является введение вспомогательной функции.
- Если мы хотим перебросить, у нас нет другого выбора, кроме как поместить код в обработчик перехвата 'Exception', который сломает FxCop и необработанные исключения вашего отладчика.