到達できないコードですが、例外はあります


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明示的に、しかし置くusingusing (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
ドミトリーBychenko

7
finallyブロックは、あなたが考えているよりも何かを意味します。
するThorbjörnRavnアンデルセン

回答:


149

コンパイラの警告(レベル2)CS0162

到達不能コードが検出されました

コンパイラーは、決して実行されないコードを検出しました。

つまり、コンパイラーは静的分析を通じて十分に理解できず、到達できず、コンパイルされたILから完全に除外されます(したがって警告)

:デバッガーで到達不能コードにステップインするか、ILエクスプローラーを使用して、この事実を自分で証明できます。

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;

ドキュメントを引用するには

try-finally(C#リファレンス)

finallyブロックを使用すると、tryブロックに割り当てられているリソースをクリーンアップでき、tryブロックで例外が発生した場合でもコードを実行できます。通常、finallyブロックのステートメントは、制御がtryステートメントを離れたときに実行されます。制御の移動は、通常の実行、break、continue、goto、またはreturnステートメントの実行、またはtryステートメントからの例外の伝播の結果として発生する可能性があります。

処理された例外内では、関連付けられたfinallyブロックが実行されることが保証されています。ただし、例外が処理されない場合、finallyブロックの実行は、例外の巻き戻し操作がどのようにトリガーされるかに依存します。これは、コンピュータのセットアップ方法に依存しています。

通常、未処理の例外がアプリケーションを終了するとき、finallyブロックが実行されるかどうかは重要ではありません。ただし、その状況でも実行する必要があるfinallyブロックにステートメントがある場合、1つの解決策は、try-finallyステートメントにcatchブロックを追加することです。または、呼び出しスタックの上位にあるtry-finallyステートメントのtryブロックでスローされる可能性のある例外をキャッチすることもできます。つまり、try-finallyステートメントを含むメソッドを呼び出すメソッド、そのメソッドを呼び出すメソッド、または呼び出しスタック内の任意のメソッドで例外をキャッチできます。例外がキャッチされない場合、finallyブロックの実行は、オペレーティングシステムが例外の巻き戻し操作をトリガーするかどうかによって異なります。

最後に

IDisposableインターフェイス(アンマネージリソースを解放するように設計されています)をサポートするものを使用する場合は、それをusingステートメントでラップできます。コンパイラーはオブジェクトを生成しtry {} finally {}、内部的Dispose()にオブジェクトを呼び出します


1
最初の文でILとはどういう意味ですか?
時計じかけ

2
@Clockwork ILは、高レベルの.NET言語で記述されたコードをコンパイルしたものです。これらの言語のいずれかで記述されたコードをコンパイルすると、ILから作成されたバイナリが取得されます。。中間言語は時々も共通中間言語(CIL)またはMicrosoft中間言語(MSIL)と呼ばれることに注意してください、
TheGeneral

1
簡単に言えば、彼は可能性をキャッチしなかったので、次のとおりです:returnに到達するまでtryが実行され、最終的に以下のreturnを無視するか、例外がスローされ、関数が例外のために終了するため、returnに到達しない投げた。
フェリペ

86

finallyブロックが実行され、次にfalseが返されます。下部に。

違う。finally例外を飲み込みません。それはそれを尊重し、例外は通常通りスローされます。それは、ブロックが終了する前に(例外の有無にかかわらず)最終的にのみコードを実行します。

例外を飲み込みたい場合はcatch、そのthrow中にnoのないブロックを使用する必要があります。


1
上記のsinppetは例外の場合にコンパイルされますが、何が返されますか?
Ehsan Sajjad

3
コンパイルはできますが、return false代わりに例外がスローされるため、ヒットすることはありません@EhsanSajjad
Patrick Hofman

1
奇妙に思われますが、例外がない場合はboolの値を返し、例外の場合は何も返さないため、コンパイルされるので、メソッドの戻り値の型を満たすことは正当ですか?
Ehsan Sajjad

2
コンパイラーはその行を無視します。それが警告の対象です。では、なぜそれが奇妙なのでしょうか。@EhsanSajjad
Patrick Hofman

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 { ... }

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ブロックが実行され、次にfalseが返されます。下部に。

finallyブロックが実行され、キャッチされない例外が発生するため、これは誤りです。

finallyブロックはクリーンアップに使用され、例外をキャッチしません。例外が返される前にスローされるため、例外が前にスローされるため、戻り値に到達することはありません。

例外がスローされるため、IDEは正しく到達しません。catch例外をキャッチできるのはブロックのみです。

ドキュメントから読む、

通常、未処理の例外がアプリケーションを終了するとき、finallyブロックが実行されるかどうかは重要ではありません。ただし、その状況でも実行する必要があるfinallyブロックにステートメントがある場合、1つの解決策は、try-finallyステートメントにcatchブロックを追加することです。または、呼び出しスタックの上位にあるtry-finallyステートメントのtryブロックでスローされる可能性のある例外をキャッチすることもできます。つまり、try-finallyステートメントを含むメソッドを呼び出すメソッド、そのメソッドを呼び出すメソッド、または呼び出しスタック内の任意のメソッドで例外をキャッチできます。例外がキャッチされない場合、finallyブロックの実行は、オペレーティングシステムが例外の巻き戻し操作をトリガーするかどうかによって異なります

これは、finallyが例外をキャッチすることを意図していないことを明確に示しており、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ブロックが実行され、次にfalseが返されます。底に

finallyブロックが例外をキャッチせず、最後のreturnステートメントに到達しないため、これはロジックの欠陥です。


4

最後のステートメントにreturn falseは到達できません。これは、tryブロックにcatch例外を処理する部分がないため、finallyブロックの後で例外が再スローされ、実行が最後のステートメントに到達することはないためです。


2

コードに2つのリターンパスがあり、最初のパスが原因で2番目のパスに到達できない。tryブロックの最後のステートメントはreturn returnValue == 1;通常の戻りを提供するためreturn false;、メソッドブロックの最後に到達することはできません。

FWIW、finallyブロックに関連する実行順序は次のとおりです。try ブロックで戻り値を提供する式が最初に評価され、次にfinallyブロックが実行され、次に計算された式の値が(tryブロック内で)返されます。

なし...例外の流れについてはcatchfinally例外は、メソッドのうち再スローされる前に、例外時に実行されます。「リターン」パスはありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.