2009-08-01 83 views
77

我認爲這將是很好做這樣的事情(與拉姆達做產返程):在C#中,爲什麼匿名方法不能包含yield語句?

public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new() 
{ 
    IList<T> list = GetList<T>(); 
    var fun = expression.Compile(); 

    var items =() => { 
     foreach (var item in list) 
      if (fun.Invoke(item)) 
       yield return item; // This is not allowed by C# 
    } 

    return items.ToList(); 
} 

然而,我發現我不能使用匿名方法的產量。我想知道爲什麼。 yield docs只是說這是不允許的。

由於它不被允許,我只是創建列表並添加項目。

+0

,我們可以有匿名`async` lambda表達式允許`await`內部在C#5.0中,我很想知道爲什麼他們仍然避風港在裏面實現了帶有yield的匿名迭代器。或多或少,它是一樣的狀態機生成器。 – Noseratio 2014-03-13 09:26:22

回答

91

埃裏克利珀最近寫了一系列關於爲什麼產量不某些情況下允許的博客文章。

EDIT2:

  • Part 7(這個後來被張貼和專門解決這個問題)

你也許會找到答案......


EDIT1:這是在解釋第5部分的評論,在埃裏克的回答Abhijeet Patel的評論:

問:

埃裏克,

你能還提供一些見解 爲什麼 「收益率」 是不允許的 匿名方法或lambda表達式

A內:

問得好。我很想擁有 匿名迭代器塊。這將是 完全真棒能夠建立 自己一個小序列發生器 就地關閉了當地 變量。之所以不是 直截了當:好處不是 大於成本。 使得序列發生器在原地的可怕性爲 實際上在很大的 方案和名義方法 在大多數 方案中做得不夠好。所以好處不是 ,這是很有吸引力的。

成本很大。迭代器 重寫是編譯器中最複雜的 變換,而 匿名方法重寫是 第二複雜。匿名 方法可以在其他匿名方法裏面,而匿名方法可以在 裏面迭代器塊。因此,我們所做的是首先我們重寫所有的 匿名方法,以便它們變爲 閉包類的方法。這是 編譯器 在爲方法發射IL之前所做的第二件事。 完成該步驟後,迭代器 重寫器可以假定在迭代器 塊中不存在 匿名方法;他們都已被重寫 已經。因此,迭代器 重寫器可以專注於 重寫迭代器,而不是 擔心在那裏可能存在未實現的匿名方法。

此外,迭代器塊永遠不會「嵌套」,與匿名方法不同, 。重寫器 重寫器可以假設所有迭代器 塊都是「頂層」。

如果匿名方法允許 包含迭代器塊,那麼這兩個 這些假設出去的窗口。 你可以有一個迭代塊 包含 包含 包含迭代塊 包含匿名方法匿名方法匿名方法,並... 呸。現在我們必須編寫一個重寫 的pass,可以同時處理嵌套迭代器 塊和嵌套匿名方法,同時將我們兩個最複雜的算法合併成一個更復雜的算法。這將是 真的很難設計,實施, 和測試。我確信,我們足夠聰明,可以做 。我們在這裏有一個聰明的球隊 。但我們不想承擔 這個「很高興有 但沒有必要」的特性。 - 埃裏克

+0

有趣,特別是因爲現在有地方功能。 – Mafii 2017-11-24 12:18:46

2

不幸的是,我不知道他們爲什麼不允許這樣做,因爲當然完全可以設想這將如何工作。

但是,匿名方法已經是一種「編譯器魔術」,意思是方法將被提取到現有類中的方法,或者甚至提取到一個全新的類,這取決於它是否處理本地變量與否。

此外,使用yield的迭代器方法也使用編譯器魔術來實現。

我的猜測是,這兩個中的一個使得代碼對另一塊魔法無法識別,並且決定不花時間爲當前版本的C#編譯器做這件事。當然,它可能根本不是一個明智的選擇,而且它不起作用,因爲沒有人會考慮實施它。

對於100%準確的問題,我會建議你使用Microsoft Connect網站和報告問題,我敢肯定你會得到有用的東西作爲回報。

18

埃裏克利珀對iterator blocks

特別是迭代器塊寫了一個極好的一系列關於限制(和設計決策影響者的選擇)的文章通過一些複雜的編譯器代碼轉換中實現。這些轉變將與匿名函數或lambda表達式,從而在某些情況下,他們將都試圖「轉換」的代碼轉換成一些其他的結構,它是與其他不兼容其內部發生的變革影響。

因此,他們被禁止互動。

迭代塊引擎蓋下是如何工作的是處理好here

由於不兼容的一個簡單的例子:

public IList<T> GreaterThan<T>(T t) 
{ 
    IList<T> list = GetList<T>(); 
    var items =() => { 
     foreach (var item in list) 
      if (fun.Invoke(item)) 
       yield return item; // This is not allowed by C# 
    } 

    return items.ToList(); 
} 

編譯器同時希望將其轉換爲類似:

// inner class 
private class Magic 
{ 
    private T t; 
    private IList<T> list; 
    private Magic(List<T> list, T t) { this.list = list; this.t = t;} 

    public IEnumerable<T> DoIt() 
    { 
     var items =() => { 
      foreach (var item in list) 
       if (fun.Invoke(item)) 
        yield return item; 
     } 
    } 
} 

public IList<T> GreaterThan<T>(T t) 
{ 
    var magic = new Magic(GetList<T>(), t) 
    var items = magic.DoIt(); 
    return items.ToList(); 
} 

,並在同一時間迭代器方面努力做這是製作一個小型狀態機的工作。某些簡單的例子可能具備相當健全檢查(先處理(可能是任意nexted閉包),然後看的工作,如果造成階級最底層水平有可能轉化爲迭代器狀態機。

但是這將是

  1. 相當多的工作。
  2. 如果沒有至少迭代器塊方面能夠阻止閉包方面爲效率應用某些轉換(如將局部變量提升爲實例變量而不是完全成熟的閉包類),那麼在所有情況下都可能無法工作。
    • 如果甚至有輕微的重疊機會,如果不可能實現或很難實現,那麼支持問題的數量可能會很高,因爲許多用戶會失去微妙的重大改變。
  3. 它可以很容易解決。

在你的榜樣,像這樣:

public IList<T> Find<T>(Expression<Func<T, bool>> expression) 
    where T : class, new() 
{ 
    return FindInner(expression).ToList(); 
} 

private IEnumerable<T> FindInner<T>(Expression<Func<T, bool>> expression) 
    where T : class, new() 
{ 
    IList<T> list = GetList<T>(); 
    var fun = expression.Compile(); 
    foreach (var item in list) 
     if (fun.Invoke(item)) 
      yield return item; 
} 
+1

沒有明確的理由說明,爲什麼編譯器在解除所有閉包後不能執行通常的迭代器轉換。你知道一個實際上會帶來一些困難的案例嗎?順便說一句,你的'魔術'類應該是'Magic `。 – Qwertie 2013-04-17 18:05:21

1

我這樣做:

IList<T> list = GetList<T>(); 
var fun = expression.Compile(); 

return list.Where(item => fun.Invoke(item)).ToList(); 

當然,你需要從.NET 3.5中引用的LINQ的方法System.Core.dll。其中包括:

using System.Linq; 

乾杯,

狡猾現在

相關問題