109

以下は大丈夫です:

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部分は(現在)何もスローできませんが、なぜそれが、前後にスローされた例外を処理するためにtry/を囲むことを停止する必要がありますか?catchyield return

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

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

4

4 に答える 4

54

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

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

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

これらの各ケースでは、コンパイラがさらに複雑になるという犠牲を払って、もう少し自由度を高めることができます。チームは実用的な選択をしました。私は彼らに拍手を送ります - 私はむしろ、99.9% の精度のコンパイラを備えた、もう少し制限の厳しい言語を使用したいと考えています (そうです、バグがあります。つい先日、SO でバグに遭遇しました)。正しくコンパイルできなかった柔軟な言語。

編集:これが実現可能である理由の疑似証明です。

次のことを考慮してください。

  • yield 戻り部分自体が例外をスローしないことを確認できます (値を事前に計算してから、フィールドを設定して「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");

(疑似コードの一種)に:

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 ブロックの設定にありますが、これはコンパイラが確実に実行できることです。

ここで何かを見逃している可能性があります - もしそうなら、私に知らせてください!

于 2008-12-06T21:07:02.020 に答える
6

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

于 2008-12-06T16:15:59.360 に答える
3

Microsoft の誰かがこのアイデアに冷水を注ぐまで、THE INVINCIBLE SKEET の回答を受け入れました。しかし、意見の問題の部分には同意しません。もちろん、完全なコンパイラよりも正しいコンパイラの方が重要ですが、C# コンパイラは、この変換を可能な限りうまく整理してくれます。この場合、もう少し完全にすることで、言語を使いやすく、教えやすく、説明しやすくなり、エッジ ケースや落とし穴が少なくなります。ですから、余分な努力をする価値があると思います。レドモンドの数人の男が 2 週間頭を悩ませ、その結果、今後 10 年間で何百万人ものプログラマーがもう少しリラックスできるようになります。

(私はまた、反復を駆動するコードによって、「外部から」状態マシンに詰め込まれた例外をスローする方法があることを切望しyield returnています。しかし、これを望む理由は非常にあいまいです。)

実際、ジョンの答えについて私が持っている質問の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");

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

于 2008-12-07T00:38:43.917 に答える
2

列挙子からのリターンを生成するときにコールスタックが巻き上げ/巻き戻しされる方法のために、try/catchブロックが実際に例外を「キャッチ」することは不可能になると推測します。(彼が反復ブロックを開始したにもかかわらず、yield returnブロックがスタックにないため)

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

于 2008-12-06T15:43:20.643 に答える