2012-03-09 91 views
17

爲什麼我們不能同時使用return和yield return?爲什麼不能在同一個方法中使用「return」和「yield return」?

例如,我們可以在下面有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

等一下,jon雙向飛碟現在會來。 – Juvanis 2012-03-09 08:52:38

+0

我會補充一點,如果你真的需要它,你可以創建一個GetIngegers4,根據條件調用GetIntegers1或GetIntegers2。 – xanatos 2012-03-09 09:03:27

+0

這可能是顯而易見的,但在這種情況下,您總是可以展開您的收藏並返回物品: foreach(var in new [] {4,5,6}) yield return item; – Foo42 2012-03-09 09:04:18

回答

16

return渴望。它一次返回整個結果集。 yield return建立一個枚舉器。在幕後,當您使用yield return時,C#編譯器會爲枚舉器發出必要的類。編譯器在確定它是否應該爲枚舉發出代碼或者有一個返回簡單數組的方法時,不會查找運行時條件(如if (someCondition))。它檢測到在你的方法中你使用了兩種方法,這是不可能的,因爲他不能爲枚舉器發出代碼,同時該方法返回一個普通的數組,並且所有這些都用於相同的方法。

+0

當我嘗試調試代碼時,我可以看到它遍歷迭代中的所有代碼行。那麼我們怎麼能說它在內部建立了枚舉器並且只返回它一次...... – Karan 2012-05-21 10:25:15

7

編譯器用yield語句(返回或中斷)重新編寫任何方法。它目前無法處理可能或不可能的方法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。相反,你需要使用2種方法:

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

從理論上講,我認爲沒有理由爲什麼返回和收益回報不能混合:編譯器首先將語句任何return (blabla());句子轉換爲:

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

然後繼續(將整個方法轉換成...現在轉換它的任何東西;內部匿名的IEnumerator類?)

那麼,爲什麼他們不選擇來實現它,這裏有兩個猜測:

  • 他們可能已經決定這將是混亂的用戶有兩種回報,產量回報一次,

  • 返回整個enumerable更快,更便宜,但也渴望;通過產量回報構建更爲昂貴(尤其是如果遞歸調用,請參閱Eric Lippert在帶有yield return語句的二叉樹中遍歷的警告,例如:https://stackoverflow.com/a/3970171/671084 )但是很懶。所以用戶通常不想混合這些:如果你不需要懶惰(即你知道整個序列)不會受到效率損失,只需使用一種常規方法即可。他們可能想迫使用戶按照這些思路思考。

另一方面,似乎有些情況下用戶可以從某些語法擴展中受益;你可能想(有同樣的動機不一樣的問題,但一個可能)來讀取這個問題和答案爲例:Yield Return Many?

+0

這實際上是對「微軟決定不支持的原因有什麼理由......」的回答,它本來會更好問題擺在首位。 – 2016-04-07 15:24:52

0

我認爲主要的原因這是行不通的,是因爲在某種程度上設計它是不是過於複雜,但在同一時間高性能將是困難的,相對較少的好處。

你的代碼到底會做什麼?它會直接返回數組,還是迭代它?

如果它會直接返回數組,那麼在允許的條件爲return的條件下,您將不得不考慮複雜的規則,因爲yield return之後的return沒有意義。而且您可能需要生成複雜的代碼來決定該方法是否會返回自定義迭代器或數組。

如果你想迭代集合,你可能需要一些更好的關鍵字。 Something like yield foreach。這實際上是考慮到的,但最終沒有實施。我想我記得閱讀的主要原因是,如果你有幾個嵌套迭代器,它實際上很難做到很好。

+1

使用嵌套迭代器很難使其表現良好,但這個問題可以用一些巧妙的方法解決;他們在C-Omega中這樣做。但是,如果你這樣做了,那麼當這些嵌套迭代器包含異常處理時,你就開始遇到正確性問題。這是一個可愛的功能主意,我希望我們能做到,但疼痛與增益的比例太高。 – 2012-03-13 16:40:07

相關問題