web-dev-qa-db-ja.com

キャッチ付きのtryブロック内にreturnが表示されないのはなぜですか?

以下は大丈夫です:

_try
{
    Console.WriteLine("Before");

    yield return 1;

    Console.WriteLine("After");
}
finally
{
    Console.WriteLine("Done");
}
_

finallyブロックは、全体の実行が完了すると実行されます(_IEnumerator<T>_は、IDisposableをサポートし、列挙が完了する前に破棄された場合でもこれを保証する方法を提供します)。

しかし、これは大丈夫ではありません:

_try
{
    Console.WriteLine("Before");

    yield return 1;  // error CS1626: Cannot yield a value in the body of a try block with a catch clause

    Console.WriteLine("After");
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}
_

(引数のために)tryブロック内のWriteLine呼び出しのいずれかによって例外がスローされたと仮定します。 catchブロックで実行を継続することの問題は何ですか?

もちろん、yield return部分は(現在)何もスローできませんが、なぜ_yield return_の前後にスローされた例外を処理するために、囲むtry/catchを持たないようにする必要があるのでしょうか?

更新:Eric Lippertからの興味深いコメント -try/finally動作を正しく実装するのにすでに十分な問題があるようです!

編集:このエラーのMSDNページは次のとおりです。 http://msdn.Microsoft.com/en-us/library/cs1x15az.aspx 。ただし、その理由は説明されていません。

89

これは実行可能性ではなく実用性の問題だと思います。この制限が実際には回避できない問題である場合は非常に少ないと思われますが、コンパイラの複雑さの追加は非常に重要です。

私がすでに遭遇したこのようないくつかのことがあります:

  • 属性をジェネリックにできない
  • XがX.Y(Xのネストされたクラス)から派生できない
  • 生成されたクラスでパブリックフィールドを使用するイテレータブロック

これらの各ケースでは、コンパイラの複雑さが増すという犠牲を払って、もう少し自由を得ることができます。チームは実用的な選択を行ったため、私は彼らに賞賛します-99.9%の正確なコンパイラで少し制限の多い言語が欲しいです(はい、バグがあります; SO =ちょうど先日)正しくコンパイルできなかったより柔軟な言語より。

編集:それがどのようにそれが実現可能であるのかの擬似証明です。

それを考慮してください:

  • Yield return部分自体が例外をスローしないことを確認できます(値を事前計算し、フィールドを設定して「true」を返すだけです)
  • イテレータブロックでyield returnを使用しないtry/catchが許可されています。
  • 反復子ブロック内のすべてのローカル変数は、生成された型のインスタンス変数であるため、コードを新しいメソッドに自由に移動できます

今すぐ変換:

try
{
    Console.WriteLine("a");
    yield return 10;
    Console.WriteLine("b");
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

into(一種の擬似コード):

case just_before_try_state:
    try
    {
        Console.WriteLine("a");
    }
    catch (Something e)
    {
        CatchBlock();
        goto case post;
    }
    __current = 10;
    return true;

case just_after_yield_return:
    try
    {
        Console.WriteLine("b");
    }
    catch (Something e)
    {
        CatchBlock();
    }
    goto case post;

case post;
    Console.WriteLine("Post");


void CatchBlock()
{
    Console.WriteLine("Catch block");
}

唯一の重複は、try/catchブロックの設定です-しかし、それはコンパイラーが確かにできることです。

私はここで何かを見逃したかもしれません-もしそうなら、私に知らせてください!

48
Jon Skeet

イテレータ定義内のすべてのyieldステートメントは、ステートマシン内の状態に変換され、switchステートメントを効果的に使用して状態を進めます。 did try/catchのyieldステートメントのコードを生成する場合、everythingtryブロックに複製する必要があります- eachyieldステートメントは、そのブロックの他のすべてのyieldステートメントを除外します。これは、特に1つのyieldステートメントが以前のステートメントに依存している場合、常に可能とは限りません。

5
Mark Cidade

列挙子からリターンを返すときにコールスタックが傷ついたり、ほどけたりするため、try/catchブロックが実際に例外を「キャッチ」することは不可能になると推測します。 (yield returnブロックは、イテレーションブロックを開始したにもかかわらず、スタック上にないため)

イテレータブロックとそのイテレータを使用するforeachのセットアップについて、私が話していることのアイデアを得るために。 foreachブロック内で呼び出しスタックがどのように見えるかを確認してから、反復子try/finallyブロック内で呼び出しスタックを確認します。

2
Radu094

Microsoftから誰かがアイデアに冷たい水を注いで来るまで、私は無敵のスキートの答えを受け入れました。しかし、私は意見の部分に同意しません-もちろん、正しいコンパイラーは完全なものよりも重要ですが、C#コンパイラーは既にこの変換を整理するのに非常に賢いです。この場合、もう少し完全にすれば、Edgeのケースや落とし穴を少なくして、言語を使いやすく、教えて、説明しやすくなります。だから私はそれは余分な努力の価値があると思います。レドモンドの数人の人が2週間頭を悩まし、その結果、次の10年間で何百万人ものコーダーがもう少しリラックスできるようになりました。

(私はまた、yield return反復を駆動するコードによって、「外部から」ステートマシンに詰め込まれた例外をスローします。しかし、これを望んでいる私の理由はかなりあいまいです。)

実際、Jonの答えについて私が持っている質問の1つは、yield return式のスローに関するものです。

明らかに、利回りリターン10はそれほど悪くはありません。しかし、これは悪いでしょう:

yield return File.ReadAllText("c:\\missing.txt").Length;

したがって、前のtry/catchブロック内でこれを評価する方が意味がありません。

case just_before_try_state:
    try
    {
        Console.WriteLine("a");
        __current = File.ReadAllText("c:\\missing.txt").Length;
    }
    catch (Something e)
    {
        CatchBlock();
        goto case post;
    }
    return true;

次の問題は、ネストされたtry/catchブロックと例外の再スローです。

try
{
    Console.WriteLine("x");

    try
    {
        Console.WriteLine("a");
        yield return 10;
        Console.WriteLine("b");
    }
    catch (Something e)
    {
        Console.WriteLine("y");

        if ((DateTime.Now.Second % 2) == 0)
            throw;
    }
}
catch (Something e)
{
    Console.WriteLine("Catch block");
}
Console.WriteLine("Post");

しかし、私はそれが可能だと確信しています...

2