以下は大丈夫です:
_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 。ただし、その理由は説明されていません。
これは実行可能性ではなく実用性の問題だと思います。この制限が実際には回避できない問題である場合は非常に少ないと思われますが、コンパイラの複雑さの追加は非常に重要です。
私がすでに遭遇したこのようないくつかのことがあります:
これらの各ケースでは、コンパイラの複雑さが増すという犠牲を払って、もう少し自由を得ることができます。チームは実用的な選択を行ったため、私は彼らに賞賛します-99.9%の正確なコンパイラで少し制限の多い言語が欲しいです(はい、バグがあります; SO =ちょうど先日)正しくコンパイルできなかったより柔軟な言語より。
編集:それがどのようにそれが実現可能であるのかの擬似証明です。
それを考慮してください:
今すぐ変換:
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ブロックの設定です-しかし、それはコンパイラーが確かにできることです。
私はここで何かを見逃したかもしれません-もしそうなら、私に知らせてください!
イテレータ定義内のすべてのyield
ステートメントは、ステートマシン内の状態に変換され、switch
ステートメントを効果的に使用して状態を進めます。 did try/catchのyield
ステートメントのコードを生成する場合、everythingのtry
ブロックに複製する必要があります- eachyield
ステートメントは、そのブロックの他のすべてのyield
ステートメントを除外します。これは、特に1つのyield
ステートメントが以前のステートメントに依存している場合、常に可能とは限りません。
列挙子からリターンを返すときにコールスタックが傷ついたり、ほどけたりするため、try/catchブロックが実際に例外を「キャッチ」することは不可能になると推測します。 (yield returnブロックは、イテレーションブロックを開始したにもかかわらず、スタック上にないため)
イテレータブロックとそのイテレータを使用するforeachのセットアップについて、私が話していることのアイデアを得るために。 foreachブロック内で呼び出しスタックがどのように見えるかを確認してから、反復子try/finallyブロック内で呼び出しスタックを確認します。
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");
しかし、私はそれが可能だと確信しています...