在C#中,为什么匿名方法不能包含yield语句?

我认为这样做会很好(用lambda做一个yield return):

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(); } 

但是,我发现我不能以匿名方式使用收益率。 我想知道为什么。 产量文档只是说这是不允许的。

由于这是不允许的,我只是创build列表并添加项目。

Eric Lippert最近撰写了一系列关于为什么在某些情况下不允许收益的博客文章。

  • 第1部分
  • 第2部分
  • 第3部分
  • 第4部分
  • 第5部分
  • 第6部分

EDIT2:

  • 第7部分 (这个是后来发布的,专门解决了这个问题)

你可能会在那里find答案…


编辑1:这在第五部分的评论中解释,在埃里克的回答Abhijeet Patel的评论:

Q:

埃里克,

你还可以提供一些有关“匿名方法”或“lambdaexpression式”中不允许使用“yield”的信息

A :

好问题。 我很想有匿名迭代器块。 如果能够自己build立一个序列发生器,就地closures局部variables,那将是非常棒的。 为什么不直截了当:收益并没有超过成本。 制造序列发生器的真棒在事情的macros伟计划中实际上是非常小的,名义方法在大多数情况下都能很好地完成工作。 所以好处并不是那么令人信服。

成本很大。 迭代器重写是编译器中最复杂的转换,匿名方法重写是第二复杂的。 匿名方法可以在其他匿名方法中,匿名方法可以在迭代器块内部。 因此,我们首先要重写所有的匿名方法,使它们成为闭包类的方法。 这是编译器在为一个方法发射IL之前所做的第二件事。 一旦完成了这一步,迭代器重写器就可以假定迭代器块中没有匿名方法; 他们都已经被重写了。 因此,迭代器重写器可以专注于重写迭代器,而不必担心那里可能存在未实现的匿名方法。

而且,与匿名方法不同,迭代器块永远不会“嵌套”。 迭代器重写器可以假定所有迭代器块都是“顶层”。

如果允许匿名方法包含迭代器块,那么这两个假设都会出现在窗口之外。 你可以有一个迭代器块,其中包含一个匿名方法,该方法包含一个匿名方法,该方法包含一个迭代器块,该块包含一个匿名方法,并且… yuck。 现在我们必须编写一个重写通道,它可以同时处理嵌套迭代器块和嵌套匿名方法,将我们两个最复杂的algorithm合并成一个更为复杂的algorithm。 这将是很难devise,实施和testing。 我敢肯定,我们很聪明。 我们在这里有一个聪明的队伍。 但是我们不想承担这样一个“好有但不必要”的特性。 – 埃里克

Eric Lippert撰写了一系列关于迭代器块的局限性(以及影响这些select的devise决策)的优秀系列文章

特别是迭代器块是由一些复杂的编译器代码转换来实现的。 这些转换会影响匿名函数或lambdaexpression式内部的转换,以至于在某些情况下,它们都会试图将代码“转换”成另一个与另一个不兼容的构造。

因此他们被禁止互动。

如何在引擎盖下工作迭代器在这里处理得很好。

作为一个不兼容的简单例子:

 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(); } 

同时迭代器方面正试图做一些小的状态机。 某些简单的例子可能适用于相当数量的理智检查(首先处理(可能是任意closures的closures),然后查看最底层的结果类是否可以转换为迭代器状态机。

但是,这将是

  1. 相当多的工作。
  2. 如果没有至less迭代器块方面能够阻止闭包方面为了效率而应用某些转换(例如将局部variables提升为实例variables而不是完全成熟的闭包类),那么不可能在所有情况下工作。
    • 如果在不可能实现或难以实施的情况下甚至有轻微的重叠机会,那么由于微妙的突破性变化将在许多用户上丧失,所产生的支持问题的数量可能会很高。
  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; } 

不幸的是,我不知道他们为什么不允许这样做,因为当然完全有可能设想这将如何工作。

然而,匿名方法已经是一种“编译器魔术”,意思是方法将被提取到现有类中的一个方法,甚至是一个全新的类,取决于它是否处理局部variables。

另外,使用yield迭代器方法也是使用编译器魔术来实现的。

我的猜测是,这两个代码中的一个使得代码无法识别另一个魔法,并决定不花时间为当前版本的C#编译器做这个工作。 当然,这可能根本不是一个明智的select,而且也没有用,因为没有人想要实现它。

对于100%准确的问题,我build议您使用Microsoft Connect网站并报告问题,我相信您会得到可用的回报。

我会这样做:

 IList<T> list = GetList<T>(); var fun = expression.Compile(); return list.Where(item => fun.Invoke(item)).ToList(); 

当然,你需要.NET 3.5引用的System.Core.dll来实现Linq方法。 并包括:

 using System.Linq; 

干杯,

狡猾