Eric Lippert撰写了一系列关于迭代器块的限制和影响这些选择的设计决策的优秀文章。
特别是,一些复杂的编译器代码转换实现了迭代器块。这些转换会影响到匿名函数或lambda内部发生的转换,以至于在某些情况下,它们都会尝试将代码“转换”为其他与之不兼容的结构。
因此,它们被禁止进行交互。
有关迭代器块的工作原理的详细信息,请参见此处。
以下是一个不兼容性的简单示例:
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;
}
return items.ToList();
}
编译器同时想把这个转换成类似以下的内容:
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();
}
同时,迭代器方面正在尝试做一些工作来创建一个小状态机。某些简单的例子可能需要进行相当多的合理性检查(首先处理(可能是任意地)嵌套的闭包),然后看看最底层的结果类是否可以转换为迭代器状态机。
然而,这将会:
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;
}
async
lambda允许内部使用await
,我很想知道为什么他们仍然没有实现带有yield
的匿名迭代器。或多或少,这是相同的状态机生成器。 - noseratio - open to work