2014-11-19 2 views
6

Рассмотрим эту программу:Изменение необработанное исключение в перекачиваемой один в финальном блоке

using System; 
static class Program { 
    static void Main(string[] args) { 
    try { 
     try { throw new A(); } 
     finally { throw new B(); } 
    } 
    catch (B) { } 
    Console.WriteLine("All done!"); 
    } 
} 

class A : Exception { } 

class B : Exception { } 

Здесь исключение типа A брошено, для которых нет ни одного обработчика. В блоке finally выбрано исключение типа B, для которого имеется обработчик. Обычно исключения, заброшенные в finally, блокируют победу, но для необработанных исключений это отличается.

При отладке отладчик прекращает выполнение при вызове A и не разрешает выполнение блока finally.

Когда не выполняется отладка (выполняется автономно из командной строки), отображается сообщение (распечатанное и диалоговое окно сбоя) об необработанном исключении, но после этого «Все сделано!» распечатывается.

При добавлении обработчика исключений верхнего уровня, который делает не что иное, как реконструировать пойманное исключение, все в порядке: нет неожиданных сообщений и «Все сделано!». печатается.

Я понимаю, как это происходит: определение того, произошло ли исключение обработчиком до того, как будут выполнены кадры finally. Это обычно желательно, и текущее поведение имеет смысл. finally блоки вообще не должны исключать исключения.

Но this other Stack Overflow question ссылается на спецификацию языка C# и утверждает, что блок finally требуется для переопределения исключения A. Читая спецификацию, я согласен, что это именно то, что он требует:

  • В текущем элементе функции, каждая из которых try заявление, охватывающую рассматривается точка броска. Для каждого оператора S, начиная с внутренним оператором Ьги и заканчивая внешний оператором Ьги следующих шаги оцениваются:
    • Если try блока S охватывает точку броска и, если S имеет один или несколько catch положения, Защелка статья рассматривается [...]
    • в противном случае, если try блока или catch блока S охватывает точку броска и если S имеет finally блок, управление передается на finally блока. Если блок finally вызывает другое исключение, обработка текущего исключения прекращается. В противном случае, когда управление достигает конечной точки блока finally, обработка текущего исключения продолжается.
  • Если обработчик исключений не находится в текущем вызове функции, то вызов функции завершается, и одно из следующих событий:
    • [...]
  • Если обработка исключений завершает все вызовы членов функции в текущем потоке, указывая на то, что поток не имеет обработчика для исключения, тогда сам поток завершается. Влияние такого прекращения определяется реализацией.

Исключение не считается необработанной, в соответствии с моим чтением спецификации, до после все вызовы функции не были прекращены, и вызовы функции не прекращается до тех пор, finally обработчики не выполняются.

Я что-то упустил, или реализация Microsoft на C# не соответствует их собственной спецификации?

+0

Комментарии для расширенного обсуждения; этот разговор был [перемещен в чат] (http://chat.stackoverflow.com/rooms/65219/discussion-on-question-by-hvd-changing-an-unhandled-exception-to-a-handled-one- я). –

+3

Формулировка немного неуклюжие, возможно, намеренно. Но спецификация CLR не оставляет никаких сомнений в этом, глава I.12.4.2.5 делает его кристально чистым, что, наконец, блоки только выполняются * после того, как он выполнил поиск и нашел обработчик. –

ответ

1

Я думаю, что проблема заключается в том, как обработка исключений исключений .NET построена поверх Structured Exception Handling, которая имеет несколько иные правила о метании внутри блока finally.

Когда исключение A происходит, SEH пытается найти первого обработчика, способного обрабатывать ваш тип исключения, а затем запускает все блоки finally, отматывая его, но на основе логики SEH таких обработчиков нет, поэтому он кричит о необработанном исключении прежде чем .NET сможет применить свое собственное правило.

Это объясняет обработчик верхнего уровня (но только тот, который может обрабатывать тип исключения A), устраняя проблему.

В IL само по себе выглядит действительно:

.method private hidebysig static void Main(string[] args) cil managed 
{ 
    .entrypoint 
    // Code size  49 (0x31) 
    .maxstack 1 
    IL_0000: nop 
    IL_0001: nop 
    .try 
    { 
    IL_0002: nop 
    .try 
    { 
     IL_0003: nop 
     IL_0004: newobj  instance void A::.ctor() 
     IL_0009: throw 
    } // end .try 
    finally 
    { 
     IL_000a: nop 
     IL_000b: newobj  instance void B::.ctor() 
     IL_0010: throw 
    } // end handler 
    } // end .try 
    catch B 
    { 
    IL_0011: pop 
    IL_0012: nop 
    IL_0013: ldstr  "B" 
    IL_0018: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_001d: nop 
    IL_001e: nop 
    IL_001f: leave.s IL_0021 
    } // end handler 
    IL_0021: nop 
    IL_0022: nop 
    IL_0023: nop 
    IL_0024: ldstr  "A" 
    IL_0029: call  void [mscorlib]System.Console::WriteLine(string) 
    IL_002e: nop 
    IL_002f: nop 
    IL_0030: ret 
} // end of method Program::Main 

Mono имеет те же проблемы http://ideone.com/VVoPx6

+0

Вы пробовали использовать код как отдельное консольное приложение? На моей машине он печатает «Необработанное исключение: A: Исключение типа« A »было выбрано.», Что определенно не является ожидаемым поведением. –

+0

Нет, я получаю диалоговое окно отчетов об ошибках Windows. Если я скажу отменить: «Все сделано!» печатается. Какую версию .Net, Windows вы используете? –

+0

Да, вы правы –

1

ответ Павла Krymets показал, что C# компилятор достаточно непосредственно переводит try/catch/finally в КСС .try/catch/finally, а комментарий Ханса Пассанта по моему вопросу указывает, где спецификация CIL требует текущего поведения. Поэтому, поскольку есть проблема, это действительно конфликт между компилятором C# и спецификацией C#.

Что-то я заметил, что компилятор Рослин включает в себя новые экспериментальные функции языка, и один из этих новых возможностей языка сделок с try/catch: он поддерживает фильтры исключений с try/catch if синтаксис:

try { 
    ... 
} 
catch (Exception e) if (...) { 
    ... 
} 

One из основных пунктов фильтров исключений заключается в том, что они запускают до любые finally блоки, чтобы определить, имеет ли исключение какой-либо обработчик вообще. Спецификация языка еще не обновлена, чтобы охватить это: спецификация языка, включенная в Visual Studio 2015 Preview, является старой спецификацией языка C# 5.0. Однако было бы трудно, если бы не было полностью невозможно указать поведение для этого таким образом, чтобы оно требовало, чтобы блоки были выполнены до того, как исключение считается необработанным. Учитывая это, я бы сказал, что вполне уверен, что не только текущее поведение преднамеренное, но и вполне уверенное, что спецификация будет обновлена, чтобы соответствовать.

Я принимаю ответ Павла Крыметца, потому что, хотя он не полностью отвечает на мой вопрос сам по себе, это самый большой шаг к полному ответу.

Смежные вопросы