Недостижимый код, но достижимый за исключением

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;
}

В чем моя ошибка понимания здесь?

0xCAFEBABE
источник
41
Боковое примечание: прямо не называть Dispose, а поставить using:using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
Дмитрий Быченко
7
finallyБлок означает что - то другое , чем вы думаете.
Thorbjørn Ravn Andersen

Ответы:

149

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

Обнаружен недостижимый код

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

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

Примечание : вы можете доказать этот факт самому себе, попробовав перейти к недоступному коду с помощью отладчика или используя IL Explorer.

finallyМожет работать на Exception , (хотя это в стороне) не меняет тот факт , (в данном случае) , то все равно будет Uncaught исключения . Следовательно, последний returnникогда не попадет в цель.

  • Если вы хотите , чтобы код , чтобы продолжить на последний return, единственный вариант, чтобы поймать за исключение ;

  • Если вы этого не сделаете, просто оставьте все как есть и удалите return.

пример

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

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

return false;

Процитировать документацию

try-finally (Справочник по C #)

Используя блок finally, вы можете очистить все ресурсы, выделенные в блоке try, и запустить код, даже если в блоке try возникнет исключение. Обычно операторы блока finally выполняются, когда элемент управления оставляет команду try. Передача управления может происходить в результате нормального выполнения, выполнения операторов break, continue, goto или return или распространения исключения из оператора try.

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

Обычно, когда необработанное исключение завершает работу приложения, не имеет значения, запущен ли блок finally или нет. Однако, если у вас есть операторы в блоке finally, которые должны выполняться даже в этой ситуации, одним из решений является добавление блока catch к оператору try-finally . В качестве альтернативы вы можете перехватить исключение, которое может быть сгенерировано в блоке try оператора try-finally выше по стеку вызовов . То есть вы можете перехватить исключение в методе, вызывающем метод, содержащий инструкцию try-finally, или в методе, вызывающем этот метод, или в любом методе в стеке вызовов. Если исключение не обнаружено, выполнение блока finally зависит от того, выберет ли операционная система запуск операции очистки исключения.

наконец

При использовании всего, что поддерживает IDisposableинтерфейс (который предназначен для высвобождения неуправляемых ресурсов), вы можете заключить это в usingоператор. Компилятор сгенерирует try {} finally {}и внутренне вызовет Dispose()объект

Майкл Рэндалл
источник
1
Что вы подразумеваете под ИЛ в первых предложениях?
Clockwork
2
@Clockwork IL - это продукт компиляции кода, написанного на языках .NET высокого уровня. Как только вы скомпилируете свой код, написанный на одном из этих языков, вы получите двоичный файл, сделанный из IL. Обратите внимание, что промежуточный язык иногда также называют общим промежуточным языком (CIL) или промежуточным языком Microsoft (MSIL).
Майкл Рэндалл,
1
Вкратце, поскольку он не уловил возможности, следующие: либо попытка выполняется до тех пор, пока не достигнет return и, таким образом, игнорируется возврат ниже, наконец, ИЛИ генерируется исключение, и этот возврат никогда не достигается, потому что функция завершится из-за того, что исключение брошен.
Felype
86

будет выполнен блок finally, а затем будет выполнено return false; внизу.

Неправильно. finallyне проглатывает исключение. Он соблюдает это, и исключение будет сгенерировано как обычно. Он выполнит код только в finally до завершения блока (с исключением или без).

Если вы хотите, чтобы исключение было проглочено, вы должны использовать catchблок без throwсимвола.

Патрик Хофман
источник
1
будет ли приведенный выше sinppet компилироваться в случае исключения, что будет возвращено?
Ehsan
3
Он компилируется, но никогда не сработает, return falseпоскольку вместо этого вызовет исключение @EhsanSajjad
Патрик Хофман,
1
кажется странным, компилируется, потому что либо он вернет значение для bool в случае отсутствия исключения, либо в случае исключения ничего не будет, так что законно для удовлетворения типа возвращаемого метода?
Ehsan
2
Компилятор просто проигнорирует строку, для этого и написано предупреждение. Так почему это странно? @EhsanSajjad
Патрик Хофман
3
Интересный факт: на самом деле не гарантируется, что блок finally будет запущен, если исключение не обнаружено в программе. Спецификация не гарантирует этого, и ранние CLR НЕ выполняли блок finally. Я думаю, что начиная с 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 { ... }
Sinatr
источник
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, а затем будет выполнено return false; внизу.

Это неправильно, потому что будет выполнен блок finally, и тогда возникнет неперехваченное исключение.

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

Ваша IDE верна, что она никогда не будет достигнута, потому что будет выброшено исключение. Только catchблоки могут перехватывать исключения.

Читая документацию ,

Обычно, когда необработанное исключение завершает работу приложения, не имеет значения, запущен ли блок finally или нет. Однако, если у вас есть операторы в блоке finally, которые должны выполняться даже в этой ситуации, одним из решений является добавление блока catch к оператору try-finally . В качестве альтернативы вы можете перехватить исключение, которое может быть сгенерировано в блоке try оператора try-finally выше по стеку вызовов. То есть вы можете перехватить исключение в методе, вызывающем метод, содержащий инструкцию try-finally, или в методе, вызывающем этот метод, или в любом методе в стеке вызовов. Если исключение не обнаружено, выполнение блока finally зависит от того, выберет ли операционная система запуск операции очистки исключения .

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

Рэй Ву
источник
7

Когда генерируется исключение, стек раскручивается (выполнение переходит из функции) без возврата значения, и любой блок catch в кадрах стека над функцией вместо этого перехватит исключение.

Следовательно, 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 false; внизу

Это недостаток вашей логики, потому что finallyблок не перехватит исключение и никогда не достигнет последнего оператора возврата.

мне JustAndrew
источник
4

Последний оператор return falseнедоступен, потому что в блоке try отсутствует catchчасть, которая обрабатывала бы исключение, поэтому исключение повторно генерируется после finallyблока, и выполнение никогда не достигает последнего оператора.

Мартин Штауфчик
источник
2

В вашем коде есть два пути возврата, второй из которых недоступен из-за первого. Последний оператор в вашем tryблоке return returnValue == 1;обеспечивает нормальный возврат, поэтому вы никогда не сможете достичь return false;конца блока метода.

FWIW, порядок выполнения, связанный с finallyблоком, следующий: сначала будет вычислено выражение, передающее возвращаемое значение в блоке try, затем будет выполнен блок finally, а затем будет возвращено вычисленное значение выражения (внутри блока try).

Что касается потока при исключении ... без a catch, finallyбудет выполняться при исключении до того, как исключение будет повторно выброшено из метода; нет "обратного" пути.

C Робинсон
источник