Недоступний код, але доступний за винятком


108

Цей код є частиною програми, яка читає і записує в базу даних, підключений до ODBC. Він створює запис у базі даних, а потім перевіряє, чи був успішно створений запис, а потім повертається true.

Моє розуміння потоку управління таке:

command.ExecuteNonQuery()документально зафіксовано, Invalid​Operation​Exceptionколи "виклик методу недійсний для поточного стану об'єкта". Тому, якщо це відбудеться, виконання tryблоку припиняється, finallyблок буде виконуватися, а потім виконується return false;внизу.

Однак мій IDE стверджує, що return false;це недоступний код. І, здається, це правда, я можу її зняти, і вона збирається без будь-яких скарг. Однак для мене це виглядає так, ніби не було би значення повернення для шляху коду, куди кидається згаданий виняток.

private static bool createRecord(String table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1;
    } finally {
        command.Dispose();
    }

    return false;
}

Яка тут моя помилка розуміння?



41
Побічна примітка: не дзвоніть Disposeпрямо, але поставте using:using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
Дмитро Биченко

7
finallyБлок означає що - то інше , ніж ви думаєте.
Thorbjørn Ravn Andersen

Відповіді:


149

Попередження компілятора (рівень 2) CS0162

Виявлений недоступний код

Компілятор виявив код, який ніколи не буде виконаний.

Що просто говорить, Компілятор достатньо розуміє за допомогою статистичного аналізу, що його неможливо досягти і повністю відхиляє його від складеного ІР (звідси ваше попередження)

Примітка . Ви можете довести цей факт самотужки, спробувавши налагодити недосяжний код за допомогою налагоджувача або скориставшись IL Explorer

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

  • Якщо ви хочете , щоб код , щоб продовжити на останній return, єдиний варіант, щоб зловити за виключення ;

  • Якщо ви цього не зробите, просто залиште його таким, яким він є, і видаліть return.

Приклад

try 
{
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    return returnValue == 1;
}
catch(<some exception>)
{
   // do something
}
finally 
{
    command.Dispose();
}

return false;

Цитувати документацію

спробувати-нарешті (C # Reference)

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

У межах оброблюваного винятку, пов'язаний остаточний блок гарантовано працює. Однак, якщо виняток є необробленим, виконання остаточно блоку залежить від того, як спрацьовує операція розмотування винятку. Це, в свою чергу, залежить від налаштування вашого комп’ютера.

Зазвичай, коли необроблений виняток закінчує програму, не важливо, чи буде запущений блок остаточно. Однак якщо у вас є висловлювання в остаточному блоці, який потрібно запустити навіть у цій ситуації, одним із варіантів є додавання блоку спіймання до оператора спробу . Крім того, ви можете зафіксувати виняток, який може бути кинутий у блок спробу оперативної спроби, що знаходиться вище в стеку викликів . Тобто ви можете вилучити виняток у методі, який викликає метод, який містить оператор спробу, нарешті, або в методі, який викликає цей метод, або в будь-якому методі в стеці викликів. Якщо виняток не вловлюється, виконання остаточного блоку залежить від того, чи операційна система вирішить запустити вимикання операції виключення.

Нарешті

Використовуючи все, що підтримує IDisposableінтерфейс (призначений для випуску некерованих ресурсів), ви можете обернути це у usingвиписці. Компілятор буде генерувати try {} finally {}і внутрішньо викликати Dispose()об'єкт


1
Що ви маєте на увазі під ІЛ у перших реченнях?
Годинник

2
@Clockwork IL - продукт компіляції коду, написаного мовами високого рівня .NET. Коли ви складете свій код, написаний однією з цих мов, ви отримаєте двійковий файл, який складається з IL. Зауважте, що проміжну мову іноді називають також загальною проміжною мовою (CIL) або проміжною мовою Microsoft (MSIL).,
Майкл Рандалл

1
Коротко кажучи, тому, що він не вловив можливості, це: або спроба запускається, поки вона не повернеться, і, таким чином, ігнорує повернення нижче, нарешті, АБО викидається виняток, і це повернення ніколи не буде досягнуто, оскільки функція вийде через те, що виняток є кинутий.
Феліпе

86

остаточний блок буде виконаний, тоді буде виконано повернення помилковим; на дні.

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

Якщо ви хочете, щоб виняток проковтнувся, вам слід використовувати catchблок, у якому немає throw.


1
чи складеться вищезгаданий синппет у випадку виключення, що буде повернено?
Ehsan Sajjad

3
Вона компілюється, але ніколи не потрапить, return falseоскільки замість неї викине виняток @EhsanSajjad
Патрік Хофман

1
здається дивним, компілюється, тому що або він поверне значення для bool у випадку без винятку, а у випадку винятку нічого не буде, тож законним для задоволення типу повернення методу?
Ехсан Саджад

2
Компілятор просто ігнорує рядок, тому саме попередження. То чому це дивно? @EhsanSajjad
Патрік Хофман

3
Факт забави: насправді не гарантується, що остаточний блок буде запущений, якщо виняток не потрапить у програму. Специфікація не гарантує цього і ранні Clrs нічого НЕ виконати , нарешті , блок. Я думаю, що починаючи з 4.0 (можливо, було і раніше), поведінка змінювалася, але інші періоди виконання можуть все-таки поводитися інакше. Робить досить дивовижну поведінку.
Voo

27

Попередження є тому, що ви не використовували, catchа ваш метод в основному написаний так:

bool SomeMethod()
{
    return true;
    return false; // CS0162 Unreachable code detected
}

Оскільки ви використовуєте finallyвиключно для розпорядження, кращим рішенням є використання usingшаблону:

using(var command = new WhateverCommand())
{
     ...
}

Цього достатньо, щоб забезпечити те, що Disposeбуде називатися. Він гарантовано викликається або після успішного виконання блоку коду, або після (до) деякого catch внизу стека викликів (батьківські дзвінки знижені, правда?).

Якщо б не було утилізації, то

try { ...; return true; } // only one return
finally { ... }

достатньо, тому що вам ніколи не доведеться повертатися falseв кінці методу (немає необхідності в цьому рядку). Ваш метод або повертає результат виконання команди ( trueабо false), або викине виняток інакше .


Розгляньте також можливість викидати власні винятки, обгортаючи очікувані винятки (перевіряйте конструктор InvalidOperationException ):

try { ... }
catch(SomeExpectedException e)
{
    throw new SomeBetterExceptionWithExplanaition("...", e);
}

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


У більшості випадків вас не цікавлять некеровані винятки. Іноді потрібно переконатися, що finallyвикликається, навіть якщо виняток не оброблений. У цьому випадку ви просто зловите його самостійно і перекиньте (див. Цю відповідь ):

try { ... }
catch { ...; throw; } // re-throw
finally { ... }

14

Здається, ви шукаєте щось подібне:

private static bool createRecord(string table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {
  [... some other code ...]

  // Using: do not call Dispose() explicitly, but wrap IDisposable into using
  using (var command = ...) {
    try {
      // Normal flow:
      command.CommandText = sb.ToString();

      // True if and only if exactly one record affected
      return command.ExecuteNonQuery() == 1;
    }
    catch (DbException) {
      // Exceptional flow (all database exceptions)
      return false;
    }
  }
}

Зауважте, finally це не проковтне жодного винятку

finally {
  // This code will be executed; the exception will be efficently re-thrown
}

// And this code will never be reached

8

У вас немає catchблоку, тому виняток все ще кидається, що блокує повернення.

остаточний блок буде виконаний, тоді буде виконано повернення помилковим; на дні.

Це неправильно, тому що, нарешті, блок буде виконаний, і тоді виникне бідне виняток.

finallyблоки використовуються для очищення, і вони не виловлюють виняток. Виняток кидається перед поверненням, тому повернення ніколи не буде досягнуто, оскільки виняток кидається раніше.

Ваш IDE правильний, що його ніколи не буде досягнуто, оскільки виняток буде кинутий. Лише catchблоки здатні фіксувати винятки.

Читаючи з документації ,

Зазвичай, коли необроблений виняток закінчує програму, не важливо, чи буде запущений блок остаточно. Однак якщо у вас є висловлювання в остаточному блоці, який потрібно запустити навіть у цій ситуації, одним із варіантів є додавання блоку спіймання до оператора спробу . Крім того, ви можете зафіксувати виняток, який може бути кинутий у блок спробу оперативної спроби, що знаходиться вище в стеку викликів. Тобто ви можете зафіксувати виняток у методі, який викликає метод, який містить оператор спробу, нарешті, або в методі, який викликає цей метод, або в будь-якому методі в стеці викликів. Якщо виняток не вловлюється, виконання остаточного блоку залежить від того, чи операційна система вирішить запустити вимикання операції виключення .

Це чітко показує, що, нарешті, не призначено виловлювати виняток, і ви були б правильні, якби catchперед finallyзаявою була порожня заява.


7

Коли буде викинуто виняток, стек буде розмотуватися (виконання буде переміщено з функції), не повертаючи значення, і будь-який блок лову в кадрах стека над функцією буде заміщати виняток замість цього.

Отже, return falseніколи не страчуватимуть.

Спробуйте вручну викинути виняток, щоб зрозуміти контрольний потік:

try {
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    // Try this.
    throw new Exception("See where this goes.");

    return returnValue == 1;
} finally {
    command.Dispose();
}

5

У вашому коді:

private static bool createRecord(String table, IDictionary<String,String> data, System.Data.IDbConnection conn, OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1; // You return here in case no exception is thrown
    } finally {
        command.Dispose(); //You don't have a catch so the exception is passed on if thrown
    }

    return false; // This is never executed because there was either one of the above two exit points of the method reached.
}

остаточний блок буде виконаний, тоді буде виконано повернення помилковим; на дні

Це вада у вашій логіці, оскільки finallyблок не застане винятку, і він ніколи не дійде до останнього оператора return.


4

Останнє твердження return falseнедоступне, оскільки в блоці спробу відсутня catchчастина, яка б обробляла виняток, тому виняток повторно скидається після finallyблоку, а виконання ніколи не досягає останнього твердження.


2

У вашому коді є два шляхи повернення, другий з яких недоступний через перший. Останній вислів у вашому tryблоці return returnValue == 1;забезпечує нормальну віддачу, тому ви ніколи не зможете дістатися доreturn false; до кінця блоку методу.

FWIW, порядок виконання, пов'язаний з finallyблоком, такий: вираз, що подає повернене значення в блоці спробу, спочатку буде оцінено, потім буде виконано остаточний блок, а потім повернеться обчислене значення виразу (всередині блоку спробу).

Що стосується потоку за винятком ... без а catch, то finallyбуде виконано за винятком до того, як виняток буде потім скинутий із методу; шляху "повернення" немає.

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