2012-03-09 12 views
17

同じ方法でリターンと利回りの両方を使用することができないのはなぜですか?同じ方法で「返品」と「利回り返却」を使用できないのはなぜですか?

たとえば、GetIntegers1とGetIntegers2を下に設定できますが、GetIntegers3は設定できません。

public IEnumerable<int> GetIntegers1() 
{ 
    return new[] { 4, 5, 6 }; 
} 

public IEnumerable<int> GetIntegers2() 
{ 
    yield return 1; 
    yield return 2; 
    yield return 3; 
} 

public IEnumerable<int> GetIntegers3() 
{ 
    if (someCondition) 
    { 
    return new[] {4, 5, 6}; // compiler error 
    } 
    else 
    { 
    yield return 1; 
    yield return 2; 
    yield return 3; 
    } 
} 
+11

秒を待って、ジョン・スカートが来るでしょう。 – Juvanis

+0

本当に必要な場合は、条件に応じてGetIntegers1またはGetIntegers2を呼び出すGetIngegers4を作成することができます。 – xanatos

+0

これはおそらく明白ですが、そのような場合はいつでもあなたのコレクションを展開してアイテムを返すことができます: foreach(new [] {4,5,6}内のvarアイテム) yield item; – Foo42

答えて

16

returnは熱心です。一度に結果セット全体を返します。 yield returnは、列挙子を作成します。シーンの裏には、yield returnを使用すると、C#コンパイラが列挙子に必要なクラスを生成します。コンパイラは、列挙型のコードを発行する必要があるかどうかを判断するときや、単純な配列を返すメソッドを持つかどうかを判断するときは、if (someCondition)などの実行時条件を検索しません。あなたのメソッドでは、列挙子のコードを出力できないと同時に、メソッドが通常の配列を返すことができず、同じメソッドのすべてを返すことができないので、両方を使用していることが検出されます。

+0

私はコードをデバッグしようとすると、私はそれが繰り返しのコードのすべての行を移動することがわかります。それでは、列挙子を内部的に構築して一度だけ返すと言うことができます... – Karan

7

コンパイラは、yieldステートメント(returnまたはbreak)を持つメソッドをすべて書き換えます。現在のところ、yieldのメソッドを処理することはできません。

Jon SkeetのC# in Depthの第6章を読んでおくことをお勧めします。そのうちの6章は無料で利用できます。イテレータブロックはかなりうまくカバーしています。

しかし、将来のバージョンのc#コンパイラではこれが不可能な理由はありません。他の.Net言語は、 'yield from'演算子(See F# yield!)の形式で同様のものをサポートしています。こうした作業はC#で存在していた場合、それはあなたがフォームでコードを書くことができるようになる:

public IEnumerable<int> GetIntegers() 
{ 
    if (someCondition) 
    { 
    yield! return new[] {4, 5, 6}; 
    } 
    else 
    { 
    yield return 1; 
    yield return 2; 
    yield return 3; 
    } 
} 
10

んが、あなたがそれを行うことはできません - 反復子ブロック(yieldで何か)は(通常のを使用することはできません非収率)return

public IEnumerable<int> GetIntegers3() 
{ 
    if (someCondition) 
    { 
    return new[] {4, 5, 6}; // compiler error 
    } 
    else 
    { 
    return GetIntegers3Deferred(); 
    } 
} 
private IEnumerable<int> GetIntegers3Deferred() 
{ 
    yield return 1; 
    yield return 2; 
    yield return 3; 
} 

ので、この特定のケースには、両方のコードがすでに他の2つの方法に存在します:

public IEnumerable<int> GetIntegers3() 
{ 
    return (someCondition) ? GetIntegers1() : GetIntegers2(); 
} 
3

は、理論的には私は理由がないと思う代わりに、あなたは2つの方法を使用する必要があります戻り値と利回りが混在することはできません:コンパイラはまず、return (blabla());文を文法的に変換して、簡単に文に変換します。

var myEnumerable = blabla(); 
foreach (var m in myEnumerable) 
    yield return m; 
yield break; 

を実行してから、続行してください。内側の匿名のIEnumeratorクラス?!)

なぜ、彼らはここで、それを実装することを選択しなかった2つの推定されている:

  • 彼らは両方のリターンを持っているユーザーに混乱することを決めている可能性があり、

  • 列挙型全体を返すことは、より迅速で安価ですが、熱心です。 yield returnを使ったビルドはもう少し高価です(特に再帰的に呼び出された場合は、Eric Lippertのバイナリツリーでの歩留りに関する警告:https://stackoverflow.com/a/3970171/671084 など)。したがって、ユーザーは通常、これらを混ぜることは望ましくありません。あなたは全体のシーケンスを知っている)効率のペナルティを受けることはありません、ちょうど通常の方法を使用してください。彼らは、ユーザーがこれらの行に沿って考えるようにしたかったかもしれません。

一方、構文拡張の恩恵を受ける状況があるようです。あなたは、例えば、(ない同じ質問が、同様の動機と、おそらく1)のように、この質問と回答を参照することもできます。Yield Return Many?

+0

これは実際には、「Microsoftがサポートしないことを決めた理由は何でしたか?最初の質問。 –

0

私はそのようにそれを設計するので、それが動作しない主な理由だと思います過度に複雑ではありませんが、同時にパフォーマンスが良いのは難しく、ほとんど利益がありません。

あなたのコードは正確に何をしますか?配列を直接返すか、それを反復処理しますか?

returnの後にyield returnが意味をなさないので、配列が直接返される場合は、どの条件がreturnであるかによって複雑なルールを考える必要があります。また、メソッドがカスタムイテレータまたは配列を返すかどうかを決定するためには、おそらく複雑なコードを生成する必要があります。

コレクションを反復処理する場合は、よりよいキーワードが必要です。 Something like yield foreach。それは実際には考えられましたが、最終的には実装されませんでした。私が主な理由を読んで覚えていると思うのは、あなたがいくつかのネストされたイテレータを持っていれば、それがうまくいくようにするのは本当に難しいということです。

+1

ネストされたイテレータでうまく動作するようにするのは難しいですが、その問題はいくつかの賢さで解決できます。彼らはC-Omegaでそうしました。しかし、そうした場合、ネストされたイテレーターに例外処理が含まれている場合、正しい問題に遭遇します。それは素敵な機能のアイデアだと私はそれを行うことができますが、痛みとゲインの比が高すぎます。 –

関連する問題