'continue'ステートメントを 'finally'ブロック内に配置できないのはなぜですか?


107

私は問題ありません。気になるだけです。次のシナリオを想像してみてください。

foreach (var foo in list)
{
    try
    {
         //Some code
    }
    catch (Exception)
    {
        //Some more code
    }
    finally
    {
        continue;
    }
}

コンパイラエラーCS0157が発生するため、これはコンパイルされません。

コントロールはfinally句の本体を離れることはできません

どうして?


7
そう。ちょっと興味があるんだけど。コンパイルできない理由が完全に理にかなっている場合、なぜ誰かがすでに意味のあることを説明してほしいのですか?=)
J. Steen 2013

14
なぜあなたは、必要があるだろうcontinue;finallyブロック?ブロックcontinue;後と同じじゃないtry -- catchですか?
bansi 2013

6
@ J.Steenいいえ、私finally/continueはこれがC#コンパイラの制限であることを知っています:-)私もこの制限の理由に興味があります。
xanatos 2013

5
@xanatos-技術的に言えば、基礎となるCILの制限です。言語仕様から:「コントロール転送は、例外処理メカニズムを除いて、catchハンドラーやfinally節に入ることが決して許可されていません。」そして、「保護された領域からのコントロール転送は、例外命令(leave、end.filter、end.catch、またはend.finally)を通じてのみ許可されます。」br分岐命令のファミリはこれを達成できません。
署名なし2013

1
@Unsigned:これも良い制限で、奇妙なことです:)
user7116

回答:


150

finallyブロックは、例外がスローされたかどうかに関係なく実行されます。例外がスローされた場合、一体何をcontinueしますか?キャッチされなかった例外は制御を別の関数に移すため、ループの実行を続けることはできません。

例外がスローされない場合でもfinally、try / catchブロック内の他の制御転送ステートメントが実行されると実行されreturnます。たとえば、同じ問題が発生します。

つまり、そのセマンティクスでfinallyは、finallyブロックの内部から外部へ制御を移すことはできません。

意図された動作をより明確にする簡単な回避策があるため、これをいくつかの代替セマンティクスでサポートすると、役立つよりも混乱しやすくなります。したがって、エラーが発生し、問題について適切に考える必要があります。C#で行われているのは、一般的な「成功の穴に投げ込む」という考えです。

C#、あなた、そして成功の場合

例外を無視して(多くの場合悪い考えです)、ループの実行を続けたい場合は、catch allブロックを使用します。

foreach ( var in list )
{
    try{
        //some code
    }catch{
        continue;
    }
}

continueキャッチされない例外がスローされない場合にのみ使用する場合はcontinue、tryブロックの外側に配置します。


12
マイクロソフトが最終的に続行を受け入れないことにした理由を理解できるので、これを答えとして受け入れます。おそらくその画像も私を納得させました:)
lpaloub 2013

20
「成功の穴に投げ込む」というアイデアを詳しく説明できますか?私はそれを取得しませんでした:-D
Ant


13
画像はジョン・スキートによって作成されました。私はここから取得しました:msmvps.com/blogs/jon_skeet/archive/2010/09/02/…–
R.

1
もちろん、ブロックcontinue内のa は、finallyブロック内で定義されたローカルループのみが続く場合は問題ありませんfinally。ただし、質問では、「外部」ループを継続しようとします。finallyブロック内に含めることができない同様のステートメントはreturnbreak(ブロックから抜け出す場合)およびgotofinallyブロックの外側のラベルに移動する場合)です。関連するJavaの説明については、Java のfinallyブロックからの戻りを参照してください。
Jeppe Stig Nielsen 2013

32

ここに信頼できる情報源があります:

continueステートメントはfinallyブロックを終了できません(セクション8.10)。continueステートメントがfinallyブロック内で発生する場合、continueステートメントのターゲットは同じfinallyブロック内にある必要があります。そうしないと、コンパイル時エラーが発生します。

これは、MSDNの8.9.2から引用されています。

ドキュメントはそれを言う:

finallyブロックのステートメントは、制御がtryステートメントを離れると常に実行されます。これは、通常の実行の結果として、break、continue、goto、またはreturnステートメントの実行の結果として、またはtryステートメントの外に例外を伝播した結果として、コントロール転送が発生する場合にも当てはまります。finallyブロックの実行中に例外がスローされた場合、例外は次の囲んでいるtryステートメントに伝播されます。別の例外が伝搬中だった場合、その例外は失われます。例外を伝播するプロセスについては、throwステートメントの説明(8.9.5節)で詳しく説明しています。

ここからです8.10 tryステートメント


31

理にかなっていると思うかもしれませんが、実際に意味がありません

foreach (var v in List)
{
    try
    {
        //Some code
    }
    catch (Exception)
    {
        //Some more code
        break; or return;
    }
    finally
    {
        continue;
    }
}

例外がスローされたときに、何を中断または継続するつもりですか?C#コンパイラチームは、breakまたはを想定して自分で決定を下したくありませんcontinue。代わりに、開発者の状況がから制御を移すのは曖昧であると文句を言うことにしましたfinally block

したがって、コンパイラが何かを想定するのではなく、開発者が何をするつもりかを明確に述べるのは開発者の仕事です。

これがコンパイルされない理由を理解していただければ幸いです!


関連する注意として、「キャッチ」がなかったとしても、finallyステートメントに続く実行パスcatchは、が例外を介して終了したかどうかに影響され、それがとどのように相互作用するかを示すメカニズムがありませんcontinue。しかし、私はあなたの例が好きです。なぜなら、それはさらに大きな問題を示しているからです。
スーパーキャット2013

@supercat同意します。私の答えは、コンパイラーのあいまいな状況の例を示していますが、このアプローチにはまだ多くの問題があります。
Sriram Sakthivel

16

他の人が述べたように、例外に焦点を当てたように、それはコントロールを移すことの曖昧な処理についてです。

あなたの心の中で、あなたはおそらくこのようなシナリオを考えているでしょう:

public static object SafeMethod()
{
    foreach(var item in list)
    {
        try
        {
            try
            {
                //do something that won't transfer control outside
            }
            catch
            {
                //catch everything to not throw exceptions
            }
        }
        finally
        {
            if (someCondition)
                //no exception will be thrown, 
                //so theoretically this could work
                continue;
        }
    }

    return someValue;
}

理論的には、制御フローを追跡して、はい、これで「大丈夫」と言うことができます。例外はスローされず、コントロールは転送されません。ただし、C#言語の設計者には他にも問題があった。

スローされた例外

public static void Exception()
{
    try
    {
        foreach(var item in list)
        {
            try
            {
                throw new Exception("What now?");
            }
            finally
            {
                continue;
            }
        }
    }
    catch
    {
        //do I get hit?
    }
}

恐怖の後藤

public static void Goto()
{
    foreach(var item in list)
    {
        try
        {
            goto pigsfly;
        }
        finally
        {
            continue;
        }
    }

    pigsfly:
}

リターン

public static object ReturnSomething()
{
    foreach(var item in list)
    {
        try
        {
            return item;
        }
        finally
        {
            continue;
        }
    }
}

ブレイクアップ

public static void Break()
{
    foreach(var item in list)
    {
        try
        {
            break;
        }
        finally
        {
            continue;
        }
    }
}

したがって、結論として、はい、コントロールが転送されていない状況でaを使用する可能性は少しあります、多くの場合(大部分?)には例外またはブロックが含まれます。言語設計者は、これが曖昧であり、コンパイル時に制御フローが転送されていない場合にのみ使用されることを確認するのは(おそらく)不可能であると感じました。continuereturncontinue


難読化中にproguardがクラッシュしたときに、Javaで同じ状況が発生しました。あなたの説明はかなり良いです。C#はコンパイル時エラーを出します-完全に理にかなっています。
Dev_Vikram 2017年

11

通常continuefinallyブロックで使用すると意味がありません。これをみて:

foreach (var item in list)
{
    try
    {
        throw new Exception();
    }
    finally{
        //doesn't make sense as we are after exception
        continue;
    }
}

「一般的に」多くのことは意味をなさない。そして、それが「特に」機能しないという意味ではありません。IE:continueループステートメントの外では意味がありませんが、サポートされていないという意味ではありません。
zerkms 2013

理にかなっていると思います。itemファイルである可能性がありますが、読み取りに失敗しました-> finallyファイルを閉じます。continue残りの処理を防止します。
jnovacho 2013

5

「これはコンパイルされず、完全に理にかなっていると思います」

まあ、そうではないと思います。

あなたが文字通り持っているとき、あなたcatch(Exception)は最終的に(そしておそらくそれさえもcontinue)必要としません。

より現実的なを持っているcatch(SomeException)場合、例外がキャッチされないとどうなりますか?あなたcontinueは一方向に行きたい、例外処理は別のものにしたい。


2
finally持っているときに必要になると思いますcatch。信頼できる方法でリソースを閉じるために一般的に使用されます。
jnovacho 2013

しかし、それは単なる例外ではありません。またはブロック内にステートメントを簡単に置くことができreturnます。さて何をしようか?ループに戻るか、ループを続行しますか?(?我々は継続しない場合や、それがその後、我々はすぐ外継続反復するために最後の項目何場合EDIT?とリターンは決して起こらない):あるいはループ外のラベルに、ほとんどすべてのアクションは、転送は、外部の制御というループ。trycatchforeachgotoforeach
Chris Sinclair 2013

2
「文字通りcatch(Exception)がある場合、finally(そしておそらくcontinueも必要ない)は必要ありません」には同意しません。例外が発生したかどうかに関係なく、操作を実行する必要がある場合はどうなりますか?
lpaloub 2013

はい、最終的returnには、ブロック内の追加のに対応します。ただし、通常、実行はキャッチオールの後に続行されます。
Henk Holterman、2013

「最終的に」は「キャッチ」ではありません。クリーンアップコードに使用する必要があります。例外がスローされたかどうかに関係なく、finallyブロックが実行されます。ファイルを閉じる、メモリを解放する(アンマネージコードを使用している場合)などのようなものです。これらは、finallyブロックに挿入するものです
Robotnik

3

finallyブロックの本体から離れることはできません。これには、break、return、および場合によってはcontinueキーワードが含まれます。


3

finallyブロックが再スローされるのを待っている例外で実行することができます。continue例外を再スローせずに(他の何かによって)ブロックを終了できることは、実際には意味がありません。

何が起こってもループを続けたい場合は、finallyステートメントは必要ありません。例外をキャッチし、再スローしないでください。


1

finallyキャッチされない例外がスローされるかどうかに関係なく実行されます。他の人はこれがなぜcontinue非論理的なのかをすでに説明していますが、このコードが求めているように見えるものの精神に従う代替案があります。基本的にfinally { continue; }は言っています:

  1. 例外がキャッチされた場合、続行します
  2. キャッチされない例外がある場合、それらがスローされることを許可しますが、引き続き続行します

(1)continue各の最後に配置することで満たすことができcatch、(2)後でスローされるキャッチされない例外を格納することで満たすことができます。次のように書くことができます:

var exceptions = new List<Exception>();
foreach (var foo in list) {
    try {
        // some code
    } catch (InvalidOperationException ex) {
        // handle specific exception
        continue;
    } catch (Exception ex) {
        exceptions.Add(ex);
        continue;
    }
    // some more code
}
if (exceptions.Any()) {
    throw new AggregateException(exceptions);
}

実際にfinallyは、3番目のケースでも実行され、例外がスローされたり、キャッチされたりキャッチされなかったりすることはありませんでした。それが必要な場合は、もちろん、continueそれぞれの内部ではなく、try-catchブロックの後にシングルを配置することもできますcatch


1

技術的には、これは基礎となるCILの制限です。言語仕様から:

コントロール転送は、例外処理メカニズムを除いて、catchハンドラーまたはfinally節に入ることが決して許可されていません。

そして

保護された領域のうち制御転送は唯一の例外命令を介して許可された(leaveend.filterend.catch、またはend.finally

br手順のドキュメントページ:

この命令では、try、catch、filter、finallyブロックへの制御の転送を実行できません。

この最後はに当てはまる全ての分岐命令を含むbeqbrfalseなど


-1

言語の設計者は、finallyブロックのセマンティクスがコントロールの転送によって終了されることを理由にしたくなかった(またはできなかった)だけでした。

1つの問題、またはおそらく重要な問題は、finallyブロックが一部の非ローカル制御転送(例外処理)の一部として実行されることです。そのコントロール転送のターゲットは、囲んでいるループではありません。例外処理はループを中止し、さらに巻き戻しを続行します。

finallyクリーンアップブロック外のコントロール転送がある場合、元のコントロール転送は「ハイジャック」されています。それは取り消され、制御は別の場所に移動します。

セマンティクスを計算することができます。他の言語にもあります。

C#の設計者は、静的な「gotoのような」コントロール転送を単に許可しないことを決定しました。

ただし、それを行ったとしても、動的転送がから開始された場合にどうなるかという問題は解決されません。 finallyブロックが関数を呼び出し、その関数がスローした場合はどうなりますか?その後、元の例外処理は「ハイジャック」されます。

このハイジャックの2番目の形式のセマンティクスを理解する場合、最初のタイプを追放する理由はありません。それらは実際には同じものです。コントロール転送は、同じ字句スコープであるかどうかにかかわらず、コントロール転送です。


差があることがfinally定義によって必要がありすぐにそれをトリガし、転送(キャッチされない例外、継続することによって追跡することreturn、など)。許可するように簡単だろう内の転送「などの後藤-」finally(ちなみに、これを行うにどの言語?)、それが意味するだろうそうtry { return } finally { ... } 返されないことがあり、完全に予想外です。finallyがスローする関数を呼び出す場合、例外はいつでも発生し、通常のフローを中断する可能性があることをすでに想定しているため、実際には同じことではありません。
nmclean 2013

@nmclean:実際、エスケープする例外finallyは、予期しない非論理的なプログラムフローを引き起こす可能性があるという点で、まったく同じです。唯一の違いは、言語デザイナーは、finallyブロックが未処理の例外をスローする可能性があるすべてのプログラムを拒否できず、代わりにそのようなプログラムのコンパイルを許可し、プログラムが以前の例外の巻き戻しの放棄に続く可能性のある結果を受け入れることを期待することです。シーケンス。
スーパーキャット2013

@supercat私はそれが予想されるフローを壊すことに同意しますが、私のポイントは、現在のフローがそうであるかどうかにかかわらず、未処理の例外の場合は常にそうであるということですfinally。Kazの最後の段落のロジックにより、関数はcontinue、例外を介して許可されているため、呼び出し元のスコープ内のフローなどに自由に影響を与えることができます。しかし、これはもちろん許可されていません。例外はこのルールの唯一の例外であるため、「例外」(または「割り込み」)という名前です。
nmclean 2013

@nmclean:ネストされていない例外は、通常の実行とは異なる制御フローを表しますが、まだ構造化されています。ブロックに続くステートメントは、そのブロック内で発生したすべての例外がそのブロック内でキャッチされた場合にのみ実行されます。それは妥当な期待のように思えるかもしれませんが、finallyブロック内で発生する例外はそれに違反する可能性があります。本当に厄介な状況です。IMHOは、finallyブロックに保留中の例外を知らせて、複合例外オブジェクトを構築できるようにすることで解決する必要があります。
スーパーキャット2013

@mmclean申し訳ありませんが、「例外」という名前がC#やLOLに固有の「制御の例外」(制御に関して)に由来することに同意しません。例外の制御フロー変更の側面は、単に非ローカルの動的制御転送です。同じ字句スコープ内での通常の制御転送(巻き戻しは発生しない)は、その特殊なケースと見なすことができます。
Kaz 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.