Використовуйте блок "спробуй остаточно" без блоку "catch"


79

Чи бувають ситуації, коли доречно використовувати try-finallyблок без catchблоку?


1
Щодо MSDN, див. Спробувати-нарешті (Довідка C #) . Зверніть увагу, що стаття посилається на спільне використання tryта finallyяк " твердження " спробувати остаточно ".
DavidRR

Відповіді:


121

Ви використовували б його, щоб переконатися, що деякі дії відбуваються після tryвмісту або щодо винятку, але коли ви не хочете використовувати цей виняток.

Щоб зрозуміти, це не приховує винятків. finallyБлок виконується перед виняток передається в стек викликів.

Ви також ненавмисно використовуєте його, коли використовуєте usingключове слово, оскільки це компілюється у try-finally(не точне перетворення, але для аргументу воно досить близько).

try
{
    TrySomeCodeThatMightException();
}
finally
{
    CleanupEvenOnFailure();
}

Запуск коду finallyне гарантовано запущений, однак той випадок, коли він не гарантований, є досить суворим - я навіть не пам’ятаю його. Все, що я пам’ятаю, це те, що якщо ви в такому випадку, дуже великі шанси, що не запуск finallyне є вашою найбільшою проблемою :-) так що в основному не потійте.

Оновлення від Tobias: finally не запускатиметься, якщо процес буде вбито.

Оновлення від Педді: умови, коли нарешті не виконується при спробі

Найпоширеніший приклад, який ви можете побачити, - це утилізація підключення до бази даних або зовнішнього ресурсу, навіть якщо код не вдається:

using (var conn = new SqlConnection("")) // Ignore the fact we likely use ORM ;-)
{
    // Do stuff.
}

Компілюється в щось на зразок:

SqlConnection conn;

try
{
    conn = new SqlConnection("");
    // Do stuff.
}
finally
{
    if (conn != null)
        conn.Dispose();
}

14
@AnthonyBlake Виняток не прихований. Якщо трапляється виняток, він запускає файл нарешті, а потім розповсюджує виняток резервною копією стека викликів.
Адам Голдсворт

2
Що стосується крайнього випадку, нарешті не буде запущено, якщо процес буде вбито (наприклад, із "завершенням процесу" в диспетчері завдань або через відключення електроенергії).
Nuffin

1
Більше інформації про те, коли остаточне твердження не спрацює - а не лише тоді, коли процес буде вбито: stackoverflow.com/questions/111597/…
Педді

1
Інший очевидний випадок, коли не все з останнього запуститься, - це те, що помилка трапляється посеред блоку нарешті.
Бен Хокінг

2
Нарешті, це не буде викликано в трьох сценаріях, які я можу придумати: вбивство процесу, переповнення стека та відсутність пам'яті. Всі вони в основному зупиняють виконання програми в той самий момент, коли вони зустрічаються, і сигналізують ОС про припинення процесу.
KeithS

5

usingеквівалентно try-finally. Ви будете використовувати лише try-finallyтоді, коли захочете провести чистку всередині finallyі не дбаєте про виняток.

Найкращий підхід буде

try
{
   using(resource)
   {
       //Do something here
   }   
}catch(Exception)
{
     //Handle Error
}

Виконавши це навіть очищення, викликане usingвідмовами, ваш код не дасть збою.

Є деякі умови, коли finallyне вдасться виконати.

  • Якщо є такі StackOverflowExceptionабо ExecutingEngineException.
  • Процес знищений із зовнішнього джерела.

Сподіваюся, це відповідає на ваші сумніви.


3

Якщо у вас є, наприклад, некерований ресурс, який ви створюєте та використовуєте в блоці try, ви можете використовувати блок нарешті, щоб забезпечити випуск цього ресурсу. Блок нарешті завжди буде виконаний, незважаючи на те, що відбувається (наприклад, винятки) у блоці try.

Наприклад, оператор lock (x) насправді такий:

System.Threading.Monitor.Enter(x); 
try { ... } 
finally 
{ 
    System.Threading.Monitor.Exit(x); 
} 

Блок нарешті завжди буде викликаний, щоб гарантувати звільнення ексклюзивного блокування.


3

Гарне пояснення з використанням коду:

void MyMethod1()
{
    try
    {
        MyMethod2();
        MyMethod3();
    }
    catch(Exception e)
    {
        //do something with the exception
    }
}


void MyMethod2()
{
    try
    {
        //perform actions that need cleaning up
    }
    finally
    {
        //clean up
    }
}


void MyMethod3()
{
    //do something
}

Якщо або MyMethod2, або MyMethod3 видають виняток, це буде виявлено MyMethod1. Однак, код у MyMethod2 повинен запустити код очищення, наприклад, закривши підключення до бази даних, перш ніж виняток буде передано MyMethod1.

http://forums.asp.net/t/1092267.aspx?Try+without+Catch+but+with+finally+doesn+t+throw+error+Why+no+syntax+error+


2
Прикладом цього "очищення" може бути saveLogFile (), який я часто хочу поставити в самий кінець програми, незважаючи ні на що.
Вінсент

1

Вам потрібен блок нарешті, коли незалежно від того, які (якщо такі є) винятки були схоплені або навіть якщо жоден не спійманий, ви все одно хочете виконати якийсь код до виходу блоку. Наприклад, ви можете закрити відкритий файл.

Див. Також спробуй нарешті


1

try / нарешті: коли ви не хочете обробляти будь-які винятки, але хочете переконатись, що виконуються певні дії, незалежно від того, чи викликає виняток викликаний код.


1

Я нічого не знаю про C #, але, здається, все, що ви могли б зробити за допомогою спроби, нарешті, ви могли б зробити більш елегантно за допомогою оператора using . С ++ навіть не має остаточного результату в результаті свого RAII .


Не обов'язково. Що робити, якщо ви хочете запустити якийсь спеціальний код, який не обробляється простим утилізацією. Наприклад, якщо ви збільшуєте лічильник у try, то зменшуєте його в a finally, ви не можете цього робити в usingоператорі.
Mark A. Donohoe

@ MarkA.Donohoe Чи не можете ви створити об'єкт, що містить посилання на лічильник, який він зменшує при утилізації?
Neil G

Так, але навіщо створювати цілий об’єкт лише для реалізації одноразового інтерфейсу, який буде використовуватися разом з оператором using? Це підходить для вирішення проблеми. Все це Try-[Catch]-Finallyобробляє, не створюючи такого об’єкта.
Mark A. Donohoe

@ MarkA.Donohoe Об’єктне рішення є вищим, оскільки воно реалізує шаблон брекетингу в одному місці. Ви не можете забути, втратити або випадково видалити код зменшення.
Neil G

Вибачте, але я не згоден. Ви зосереджуєтесь на явній декрементації поведінки, але хто сказав, що вони повинні бути ідеально підібраними? Що робити, якщо існує інша логіка збільшення / зменшення, яка також залежить від інших членів функції? Плюс, тепер ви не лише представляєте новий об’єкт лише для одного використання, але ви робите це в купі, а не в простому int у стеці. Безумовно, є час і місце для використання, usingале ви не можете зробити ковдру, як у вас є. Знову ж таки, це визначає проблему шляхом рішення, яке для мене є зворотним.
Mark A. Donohoe

1

Ось ситуація, коли ви можете скористатися спробою нарешті: коли ви зазвичай використовуєте оператор using, але не можете, оскільки ви викликаєте метод за допомогою рефлексії.

Це не спрацює

using (objMsg  =  Activator.CreateInstance(TypeAssist.GetTypeFromTypeName("omApp.MessagingBO")))
{

}

натомість використовувати

           object objMsg = null;
            try
            {
                objMsg
                   = Activator.CreateInstance(TypeAssist.GetTypeFromTypeName("myAssembly.objBO"));

                strResponse = (string)objMsg.GetType().InvokeMember("MyMethod", BindingFlags.Public
                        | BindingFlags.Instance | BindingFlags.InvokeMethod, null, objMsg,
                        new object[] { vxmlRequest.OuterXml });
            }               
            finally
            {
                if (objMsg!=null)
                    ((IDisposable)objMsg).Dispose();
            }


0

Ось варіант використання, який я завжди використовую (uhm ..):

int? x; //note the nullable type here!
try
{
    x = int.Parse(someString);
}
catch { } //don't care, let it just be null

0

1. ми можемо використовувати блок try без catch, але ми повинні використовувати catch / нарешті, будь-який з них. 2.Ми не можемо використовувати лише блок спроб.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.