Tryブロックで値を返すと、Finallyステートメントのコードが起動しますか?


237

私は友人のコードをレビューしていて、彼がtry-finallyブロック内でreturnステートメントを使用していたと言います。最後のセクションのコードは、tryブロックの残りの部分が実行されなくても実行されますか?

例:

public bool someMethod()
{
  try
  {
    return true;
    throw new Exception("test"); // doesn't seem to get executed
  }
  finally
  {
    //code in question
  }
}


ここで扱われるということは、捕まったことを意味します。グローバルハンドラの空のcatchブロックでも十分です。また、処理できない例外がありますStackOverflowExceptionExecutionEngineExceptionそれらのいくつかはあります。そして、それらは処理できないため、finally実行されません。
Abel

8
@アベル:あなたは別の状況について話しているようです。この質問は、ブロックで戻ることについてtryです。突然のプログラムの中止については何もありません。
Jon Skeet

6
@Abel:「例外の後に戻る」という意味がわかりませんが、ここで質問されているようには見えません。コードを見てください- tryブロックの最初のステートメントはreturnステートメントです。(そのブロックの2番目のステートメントは到達不能であり、警告を生成します。)
Jon Skeet

4
@Abel:確かに、そして「finallyステートメントのコードは常にあらゆる状況で常に実行されるのか」という質問だったとしたら、それは関連性のあるものでした。しかし、それは求められていたものではありません。
ジョンスキート2016

回答:


265

簡単な答え:はい。


9
@Edi:ええと、バックグラウンドスレッドがそれとどのような関係があるのか​​わかりません。基本的に、プロセス全体が異常終了しない限り、finallyブロックが実行されます。
Jon Skeet

3
長い答えは、スタックオーバーフロー、メモリ不足の例外、ある種のハードクラッシュなどの壊滅的な事態が発生した場合、または誰かが適切なタイミングでマシンのプラグを抜いた場合に、必ずしも最終的にブロックが実行されるとは限らないことです。 。しかし、すべての意図と目的のために、非常に悪いことをしない限り、finallyブロックは常に起動します。
Andrew Rollings、

なんらかの理由で試行前のコードが失敗した場合、私はついに実行されることに気づきました。その場合、あなたは基準がようやく正常に実行下の場合にのみ、コードを実行するためにどの満たし、最終的に条件を追加することができます
kjosh

200

通常、はい。finallyセクションは、例外やreturnステートメントを含め、何が起こっても実行することが保証されています。このルールの例外は、スレッドで発生する非同期例外(OutOfMemoryExceptionStackOverflowException)でです。

そのような状況での非同期例外と信頼性の高いコードの詳細については、制約付き実行領域についての説明をご覧ください。


154

ここに小さなテストがあります:

class Class1
{
    [STAThread]
    static void Main(string[] args)
    {
        Console.WriteLine("before");
        Console.WriteLine(test());
        Console.WriteLine("after");
    }

    static string test()
    {
        try
        {
            return "return";
        }
        finally
        {
            Console.WriteLine("finally");
        }
    }
}

結果は次のとおりです。

before
finally
return
after

10
@CodeBlend:WriteLineメソッド呼び出しの結果のforが実際に実行されるタイミングに関係しています。この場合、return呼び出しはメソッドの結果を設定し、最後に、メソッドが終了する前にコンソールに書き込みます。次に、WriteLineMainメソッド内でreturn呼び出しからテキストを吐き出します。
NotMe

38

MSDNからの引用

最後に、前のtryブロックの終了方法に関係なく、コードのステートメントブロックの実行を保証するために使用されます。


3
まあ、Mehrdadの例外的なケースに加えて、tryブロックでデバッグしている場合、最終的には呼び出されず、デバッグを停止します:)。人生の何も保証されていないようです。
StuartLC 2012

1
なく、かなり、引用MSDNからは例外が未処理された場合は、finallyブロックの実行は例外アンワインド操作がトリガされる方法に依存しています。これは、コンピュータの設定方法に依存しています。
Abel、

1
@アベルはここで重要なポイントを作ります。たとえば、プログラムがタスクマネージャーを介して中止された場合、finallyブロックがどのように実行されないかを誰でも確認できます。しかし、この答えは、現状では、明らかに間違っていますfinally。実際、完全に通常のプログラム(たまたまこの例外がスローされた)でさえ、何も保証されません。
ピーター-モニカの復活2016

19

一般的にはい、最終的に実行されます。

次の3つのシナリオでは、finallyは常に実行されます。

  1. 例外は発生しません
  2. 同期例外(通常のプログラムフローで発生する例外)。
    これには、System.Exceptionから派生するCLS準拠の例外と、System.Exceptionから派生しないCLS準拠以外の例外が含まれます。CLSに準拠していない例外は、RuntimeWrappedExceptionによって自動的にラップされます。C#ではCLS以外のクレーム例外をスローできませんが、C ++などの言語ではスローできます。C#は、CLSに準拠しない例外をスローできる言語で記述されたコードを呼び出す可能性があります。
  3. 非同期ThreadAbortException
    .NET 2.0以降、ThreadAbortExceptionは、finallyの実行を妨げなくなりました。ThreadAbortExceptionは、finallyの前または後にホイストされるようになりました。finallyは常に実行され、スレッドの中止が発生する前に試行が実際に入力されている限り、スレッドの中止によって中断されることはありません。

次のシナリオでは、finallyは実行されません。

非同期StackOverflowException。
.NET 2.0以降、スタックオーバーフローによりプロセスが終了します。さらに制約を適用して最終的にCER(制約付き実行領域)にしない限り、finallyは実行されません。CERは、一般的なユーザーコードでは使用しないでください。これらは、クリーンアップコードを常に実行することが重要な場合にのみ使用してください。すべてのプロセスがスタックオーバーフローでシャットダウンされ、すべての管理対象オブジェクトがデフォルトでクリーンアップされます。したがって、CERが関連する唯一の場所は、管理されていないハンドルなど、プロセスの外部に割り当てられたリソースの場合です。

通常、アンマネージコードは、ユーザーコードによって使用される前に、マネージクラスによってラップされます。マネージラッパークラスは、通常、SafeHandleを使用してアンマネージハンドルをラップします。SafeHandleは、重要なファイナライザと、クリーンアップコードの実行を保証するためにCERで実行されるReleaseメソッドを実装します。このため、CERがユーザーコード全体に散らばって表示されることはありません。

したがって、プロセスがとにかく終了するため、finallyがStackOverflowExceptionで実行されないという事実は、ユーザーコードに影響を与えません。SafeHandleまたはCriticalFinalizerObjectの外でアンマネージリソースをクリーンアップする必要がある特別なケースがある場合は、次のようにCERを使用します。ただし、これは悪い習慣です。アンマネージドコンセプトは、マネージドクラスと適切なSafeHandleに設計によって抽象化する必要があります。

例えば、

// No code can appear after this line, before the try
RuntimeHelpers.PrepareConstrainedRegions();
try
{ 
    // This is *NOT* a CER
}
finally
{
    // This is a CER; guaranteed to run, if the try was entered, 
    // even if a StackOverflowException occurs.
}

SOEの場合、CERも実行されないことに注意してください。これを書いた時点では、CERに関するMSDNドキュメントは間違っていた/不完全でした。SOEはFailFast内部でトリガーされます。私がそれらを捕らえることができた唯一の方法は、CLRランタイムホスティングをカスタマイズすることです。あなたのポイントは他のいくつかの非同期例外に対しても有効です。
Abel、

10

これには非常に重要な例外があり、他の回答では言及されていません。また、(C#で18年間プログラミングした後)知らなかったなんて信じられません。

(奇妙なものやその類のものだけでなく)ブロック内で何らかの種類の例外をスローまたはトリガーし、catchブロックStackOverflowExceptions全体try/catch/finallyが別のtry/catchブロック内finallyにない場合、ブロックは実行されません。これは簡単に示されます-そして、私がそれを自分で見ていなかった場合、finallyブロックが実行されない可能性があるのは本当に奇妙な小さなコーナーケースだと何度も読んだことを考えると、私はそれを信じていませんでした。

static void Main(string[] args)
{
    Console.WriteLine("Beginning demo of how finally clause doesn't get executed");
    try
    {
        Console.WriteLine("Inside try but before exception.");
        throw new Exception("Exception #1");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Inside catch for the exception '{ex.Message}' (before throwing another exception).");
        throw;
    }
    finally
    {
        Console.WriteLine("This never gets executed, and that seems very, very wrong.");
    }

    Console.WriteLine("This never gets executed, but I wasn't expecting it to."); 
    Console.ReadLine();
}

これには理由があると思いますが、あまり知られていないのは奇妙です。(たとえば、ここでは注記さていますが、この特定の質問のどこにも記載さていません。)


まあ、finallyブロックは単にブロックのキャッチではありませんcatch
ピーター-モニカを復活させる

7

私はパーティーに遅れていることを認識していますが、実際に例外がスローされるシナリオ(OPの例とは異なります)では、MSDNの状態(https://msdn.microsoft.com/en-us/library/zwc8s4fz.aspx):「例外がキャッチされない場合、finallyブロックの実行は、オペレーティングシステムが例外の巻き戻し操作をトリガーするかどうかによって異なります。」

finallyブロックは、呼び出しスタックのさらに上にある他の関数(Mainなど)が例外をキャッチした場合にのみ実行が保証されます。すべてのランタイム環境(CLRとOS)のC#プログラムは、プロセスが終了したときに所有するほとんどのリソース(ファイルハンドルなど)で実行されるため、通常、この詳細は問題になりません。ただし、場合によっては、それが非常に重要になることもあります。くつろぐ。または、OSによって自動的に閉じられず、サーバーをブロックするリモート接続。


3

はい。それが事実、finallyステートメントの主要なポイントです。(メモリ不足、コンピューターのプラグが外れているなど)何か異変が発生しない限り、finallyステートメントは常に実行する必要があります。


1
私は同意しません。Cf. 私の答え。finally例外がキャッチされない場合は、tryブロックの完全に通常の例外でブロックをバイパスできます。それはを驚かせました;-)。
ピーター-モニカの復活2016

@ PeterA.Schneider-面白いですね。もちろん、その意味するところはあまり変わりません。コールスタックの上の何も例外をキャッチしない場合、それは(私が正しく理解していれば)プロセスが終了しようとしていることを意味するはずです。つまり、プラグを抜く状況に似ています。これからのテイクアウトは、常にトップレベルまたは未処理の例外キャッチが必要であることです。
Jeffrey L Whitledge 16

まさに、それも私が理解していることです。それも意外だった。True:最も一般的なリソース、特にメモリと開いているファイルが自動的に解放されます。しかし、OSが認識していないリソース(例としてサーバーまたはデータベース接続を開く)は、適切にシャットダウンされたことがないため、リモート側で開いたままになる可能性があります。ソケットは長引いている、等々。トップレベルの例外ハンドラーを持つのは賢明だと思います、はい。
ピーター-モニカの復活


2

System.exit(0);を使用してアプリケーションを終了している場合、最終的には実行されません。のように

try
{
    System.out.println("try");
    System.exit(0);
}
finally
{
   System.out.println("finally");
}

結果は次のようになります:試してください


4
あなたは間違った言語で答えていると思います、質問は約でしたc#が、これはそうであるようJavaです。それに加えて、ほとんどの場合System.exit()、デザインが悪いというヒントがあります:-)
z00l

0

シナリオの99%では、finallyブロック内のコードが実行されることが保証されますが、次のシナリオを考えてください。スレッドにtry-> finallyブロック(なしcatch)があり、そのスレッド内で未処理の例外が発生します。この場合、スレッドは終了し、そのfinallyブロックは実行されません(この場合、アプリケーションは引き続き実行できます)

このシナリオはかなりまれですが、答えが常に「はい」ではないことを示すためだけであり、ほとんどの場合「はい」であり、まれに、「いいえ」の場合もあります。


0

finallyブロックの主な目的は、その中に記述されているものを実行することです。tryまたはcatchで発生したことには依存しません。ただし、System.Environment.Exit(1)を使用すると、アプリケーションは次のコード行に移動せずに終了します。

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