2013-09-14 2 views
9

Я просматривал .Net Reference Source и нашел этот драгоценный камень в ButtonBase.cs в строке 408:Есть ли причина для использования Try/Наконец с переменной ExceptionThrown над Try/Вылов

bool exceptionThrown = true; 
try 
{ 
    OnClick(); 
    exceptionThrown = false; 
} 
finally 
{ 
    if (exceptionThrown) 
    { 
     // Cleanup the buttonbase state 
     SetIsPressed(false); 
     ReleaseMouseCapture(); 
    } 
} 

вопрос заключается в том, что бы мотивировать кого-то использовать exceptionThrown флаг над просто писать как

try 
{ 
    OnClick(); 
} 
catch 
{ 
    SetIsPressed(false); 
    ReleaseMouseCapture(); 
    throw; 
} 

это просто стилистические или есть побочный эффект мне не хватает?

+0

Похоже, было бы нормально проверить определенные пользовательские бизнес-исключения во время улова, чтобы вы могли сделать что-то еще. Это выглядит в значительной степени, чтобы разоблачить стек ни за что. пустая попытка {} catch {} хуже, чем бросок по значимым причинам, и вы просто разрушаете стек за этим. –

+0

Я думаю, что между ними нет никакой разницы. Однако, если вы пишете что-то подобное, поскольку код будет меняться сверхурочно, в какой-то момент вам понадобится информация из исключения, и вы в конечном итоге используете catch. Однако небольшие изменения. –

ответ

2

По-видимому, есть побочные эффекты для throw;, которые по-прежнему будут удалять информацию о трассировке стека, по крайней мере, на .Net 2. Я предполагаю, что этот синтаксис использовался для реализации throw; на ранних версиях фреймворка.

This blog article дает два примера того, как throw; не является эквивалентом того, что он вообще не обнаруживает исключения.

В вопросе Showing stack trace of exception that is re-thrown, rather than stack trace from throw point приведен сценарий, в соответствии с которым повторное бросание исключения вызывает различную работу в Visual Studio.

+0

Я думаю, что это настоящий ответ. Избегайте повторного выброса и, следовательно, получите точную трассировку стека. –

8

Если вы используете try, поймите, что исключение должно быть выброшено дважды. С попыткой, наконец, выбрано только одно исключение, поэтому много более эффективный , особенно , если этот код вызывается часто.

+5

Если ** ** ** ** ** ** часто **, то это не является исключительным. Производительность, оптимизирующая путь кода ошибки, обычно является низкой отдачей от инвестиций. –

+0

+1 хороший ответ, был одним из первых, кто поднял ... но я видел свои доли оптимизаций, и я должен сказать вам, что это неверно - код не намного эффективнее это всего лишь на 10-15% больше эффективности, что означает, что есть optiomaztion на месте, и на самом деле не происходит 2 стека, что происходит, как можно было бы подумать о том, чтобы повторить исключение в первом примере. [Здесь тестовое приложение] (http://ideone.com/mn84X3) выглядит как крошечный крошечный бит быстрее, чтобы использовать окончательный трюк, настолько крошечный на самом деле, что не стоит записывать обфускационный код с предложением finally. – user1416420

+0

, то, о чем мы говорим, вероятно, на примере 'try/catch' выполняется 2-3 дополнительных экземпляра, которые, учитывая, что есть реальный вызов метода внутри реального примера, это была бы нецелесообразная оптимизация и может быть проигнорирована как улучшающая способность. .. более или менее, но все же +1 для ** приятного улова ** – user1416420

11

Причина этого кода - в два раза. Да, говорит Грег, это не требует повторного исключения. Но на самом деле это не причина.

Настоящая причина - одна из семантики. Исключения не должны использоваться для управления потоком управления. То, что это делает, имеет дело с тем, что если исключение выбрасывается внутри кнопки, оно может оставить визуальное состояние кнопок «нажатым». Это не значит «обрабатывать» исключение. Это просто исправление визуальной проблемы в случае исключения исключения.

Этот код не заботится о том, что такое исключение, и он не хочет улавливать все исключения, потому что это плохая практика. Кроме того, код ничего не делает с исключением .. он просто говорит: «Эй, если мы дошли до конца функции, тогда мы все хорошо. Если бы мы этого не сделали, то давайте сбросим состояние кнопки только до быть уверенным".

Итак, это не настоящая обработка исключений, поэтому она не вызывает исключения. Он просто замечает, что было исключено исключение и выполняет некоторую очистку.

EDIT:

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

bool cleanupRequired = true; 
try 
{ 
    OnClick(); 
    cleanupRequired = false; 
} 
finally 
{ 
    if (cleanupRequired) 
    { 
     // Cleanup the buttonbase state 
     SetIsPressed(false); 
     ReleaseMouseCapture(); 
    } 
} 

EDIT:

Чтобы поддержать мой комментарий ниже, я написал следующую тестовую программу для тестирования сценариев:

static void Main(string[] args) 
{ 
    TimeSpan ts = new TimeSpan(); 
    TimeSpan ts2 = new TimeSpan(); 
    TimeSpan ts3 = new TimeSpan(); 
    TimeSpan ts4 = new TimeSpan(); 
    TimeSpan ts5 = new TimeSpan(); 
    TimeSpan ts6 = new TimeSpan(); 
    TimeSpan ts7 = new TimeSpan(); 
    TimeSpan ts8 = new TimeSpan(); 

    Stopwatch sw = new Stopwatch(); 

    // throw away first run 
    for (int i = 0; i < 2; i++) 
    { 
     sw.Restart(); 
     try 
     { 
      throw new NotImplementedException(); 
     } 
     catch 
     { 
      ts = sw.Elapsed; 
     } 
     sw.Stop(); 
     ts2 = sw.Elapsed; 

     try 
     { 
      sw.Restart(); 
      try 
      { 
       throw new NotImplementedException(); 
      } 
      finally 
      { 
       ts3 = sw.Elapsed; 
      } 
     } 
     catch 
     { 
      ts4 = sw.Elapsed; 
     } 
     sw.Stop(); 
     ts5 = sw.Elapsed; 

     try 
     { 
      sw.Restart(); 
      try 
      { 
       throw new NotImplementedException(); 
      } 
      catch 
      { 
       ts6 = sw.Elapsed; 
       throw; 
      } 
     } 
     catch 
     { 
      ts7 = sw.Elapsed; 
     } 
     sw.Stop(); 
     ts8 = sw.Elapsed; 
    } 
    Console.WriteLine(ts); 
    Console.WriteLine(ts2); 
    Console.WriteLine(ts3); 
    Console.WriteLine(ts4); 
    Console.WriteLine(ts5); 
    Console.WriteLine(ts6); 
    Console.WriteLine(ts7); 
    Console.WriteLine(ts8); 
    Console.ReadLine(); 
} 

И я получил следующие результаты (я разделить их друг от друга, чтобы сделать их более удобными для чтения):

00:00:00.0028424 
00:00:00.0028453 

00:00:00.0028354 
00:00:00.0028401 
00:00:00.0028427 

00:00:00.0028404 
00:00:00.0057907 
00:00:00.0057951 

Последние 3 показывают, что при Повторное выбрасывание исключения используя throw;, он не просто передает существующее исключение, он должен воссоздать исключение и переустановить его, взяв в два раза больше.

Как мы видим, нет существенной разницы между улавливанием исключения и не ловлей, а использованием, наконец,. Тем не менее, повторное исключение - это то, где стоимость.

Это работает в VS 2012 Update 3.

EDIT:

Задержки без отладчика. Как вы можете видеть, Повторное выбрасывание еще в два раза дороже:

00:00:00.0000149 
00:00:00.0000154 

00:00:00.0000137 
00:00:00.0000140 
00:00:00.0000146 

00:00:00.0000137 
00:00:00.0000248 
00:00:00.0000251 
+0

В любом случае, это приятный, умный фрагмент кода, который позволяет избежать затрат на стеки. Но я немного хочу переименовать 'bool' в' cleanupRequired', так как он точно скрывает, каким механизмом мы это узнали. В какой-то момент вы можете получить немного * слишком умный, а следующее, что вы знаете, кто-то на StackOverflow спрашивает всех: «WTF думали, когда они это делали»? ;-) –

+0

Это кажется скорее стилистическим различием, чем функциональным. На самом деле, похоже, что вы повторно реализуете семантику catch только из-за имени. Другой interop или исключающий код, конечно, не использует флаги для реализации очистки. – Mitch

+0

@ Митч - Я не думаю, что это стилистика вообще. OnClick(), вероятно, выполняет написанный пользователем код, поэтому он может сделать что угодно. Хотя уловка все будет работать, это лишние накладные расходы по уважительной причине. Этот код просто замечает, что вызов OnClick не возвращался нормально и выполняет некоторую очистку. Меня не волнует, что и как, так зачем ловить то, на что вы действительно не заботитесь? –

1

Ну, одна причина, я могу думать (в будущем), добавив необходимость для ловли конкретных исключение, при сохранении того же поведения очистки. Рассмотрим следующие примеры:

try 
{ 
    OnClick(); 
} 
catch(System.SomeSpecificException ex) 
{ 
    Handle(ex); 
    throw; 
    // now, we're missing the cleanup 
} 
catch 
{ 
    SetIsPressed(false); 
    ReleaseMouseCapture(); 
    throw; 
} 

против

var exceptionThrown = true; 
try 
{ 
    OnClick(); 
    exceptionThrown = false; 
} 
catch(System.SomeSpecificException ex) 
{ 
    Handle(ex); 
    throw; 
    // whoa, I added specific handler, but cleanup is called anyway! 
} 
finally 
{ 
    if (exceptionThrown) 
    { 
     // Cleanup the buttonbase state 
     SetIsPressed(false); 
     ReleaseMouseCapture(); 
    } 
} 
0

Небольшое примечание: OnClick представляет собой виртуальный метод. Ожидается, что он будет переопределен пользовательским кодом, который может вызывать произвольные исключения. В написанном коде выражается семантика «очистить от беспорядка, созданного теми, кто перепробовал этот метод».

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