在C#中,为什么匿名方法不能包含yield语句?
我认为这将是很好做这样的事情(与拉姆达做产返程):在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只是说这是不允许的。
由于它不被允许,我只是创建列表并添加项目。
埃里克利珀最近写了一系列关于为什么产量不某些情况下允许的博客文章。
EDIT2:
- Part 7(这个后来被张贴和专门解决这个问题)
你也许会找到答案......
EDIT1:这是在解释第5部分的评论,在埃里克的回答Abhijeet Patel的评论:
问:
埃里克,
你能还提供一些见解 为什么 “收益率” 是不允许的 匿名方法或lambda表达式
A内:
问得好。我很想拥有 匿名迭代器块。这将是 完全真棒能够建立 自己一个小序列发生器 就地关闭了当地 变量。之所以不是 直截了当:好处不是 大于成本。 使得序列发生器在原地的可怕性为 实际上在很大的 方案和名义方法 在大多数 方案中做得不够好。所以好处不是 ,这是很有吸引力的。
成本很大。迭代器 重写是编译器中最复杂的 变换,而 匿名方法重写是 第二复杂。匿名 方法可以在其他匿名方法里面,而匿名方法可以在 里面迭代器块。因此,我们所做的是首先我们重写所有的 匿名方法,以便它们变为 闭包类的方法。这是 编译器 在为方法发射IL之前所做的第二件事。 完成该步骤后,迭代器 重写器可以假定在迭代器 块中不存在 匿名方法;他们都已被重写 已经。因此,迭代器 重写器可以专注于 重写迭代器,而不是 担心在那里可能存在未实现的匿名方法。
此外,迭代器块永远不会“嵌套”,与匿名方法不同, 。重写器 重写器可以假设所有迭代器 块都是“顶层”。
如果匿名方法允许 包含迭代器块,那么这两个 这些假设出去的窗口。 你可以有一个迭代块 包含 包含 包含迭代块 包含匿名方法匿名方法匿名方法,并... 呸。现在我们必须编写一个重写 的pass,可以同时处理嵌套迭代器 块和嵌套匿名方法,同时将我们两个最复杂的算法合并成一个更复杂的算法。这将是 真的很难设计,实施, 和测试。我确信,我们足够聪明,可以做 。我们在这里有一个聪明的球队 。但我们不想承担 这个“很高兴有 但没有必要”的特性。 - 埃里克
有趣,特别是因为现在有地方功能。 – Mafii 2017-11-24 12:18:46
不幸的是,我不知道他们为什么不允许这样做,因为当然完全可以设想这将如何工作。
但是,匿名方法已经是一种“编译器魔术”,意思是方法将被提取到现有类中的方法,或者甚至提取到一个全新的类,这取决于它是否处理本地变量与否。
此外,使用yield
的迭代器方法也使用编译器魔术来实现。
我的猜测是,这两个中的一个使得代码对另一块魔法无法识别,并且决定不花时间为当前版本的C#编译器做这件事。当然,它可能根本不是一个明智的选择,而且它不起作用,因为没有人会考虑实施它。
对于100%准确的问题,我会建议你使用Microsoft Connect网站和报告问题,我敢肯定你会得到有用的东西作为回报。
埃里克利珀对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闭包),然后看的工作,如果造成阶级最底层水平有可能转化为迭代器状态机。
但是这将是
- 相当多的工作。
- 如果没有至少迭代器块方面能够阻止闭包方面为效率应用某些转换(如将局部变量提升为实例变量而不是完全成熟的闭包类),那么在所有情况下都可能无法工作。
- 如果甚至有轻微的重叠机会,如果不可能实现或很难实现,那么支持问题的数量可能会很高,因为许多用户会失去微妙的重大改变。
- 它可以很容易解决。
在你的榜样,像这样:
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;
}
没有明确的理由说明,为什么编译器在解除所有闭包后不能执行通常的迭代器转换。你知道一个实际上会带来一些困难的案例吗?顺便说一句,你的'魔术'类应该是'Magic
我这样做:
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;
干杯,
狡猾现在
,我们可以有匿名`async` lambda表达式允许`await`内部在C#5.0中,我很想知道为什么他们仍然避风港在里面实现了带有yield的匿名迭代器。或多或少,它是一样的状态机生成器。 – Noseratio 2014-03-13 09:26:22