Эрик Липперт написал отличную серию статей об ограничениях (и проектных решениях, влияющих на этот выбор) блоков итераторов.
В частности, блоки итератора реализуются посредством некоторых сложных преобразований кода компилятора. Эти преобразования будут влиять на преобразования, которые происходят внутри анонимных функций или лямбда-выражений, так что в определенных обстоятельствах они оба будут пытаться «преобразовать» код в какую-то другую конструкцию, несовместимую с другой.
В результате им запрещено взаимодействие.
Здесь хорошо разбирается, как работают блоки итератора .
В качестве простого примера несовместимости:
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();
}
и в то же время аспект итератора пытается сделать свою работу по созданию небольшого конечного автомата. Некоторые простые примеры могут работать с достаточным количеством проверок работоспособности (сначала имея дело с (возможно, произвольными) вложенными замыканиями), а затем проверять, могут ли результирующие классы самого нижнего уровня быть преобразованы в конечные машины итератора.
Однако это было бы
- Довольно много работы.
- Невозможно было работать во всех случаях, если, по крайней мере, аспект блока итератора не мог предотвратить применение аспектом закрытия определенных преобразований для повышения эффективности (например, продвижение локальных переменных в переменные экземпляра, а не в полноценный класс закрытия).
- Если бы существовала хотя бы небольшая вероятность совпадения, когда это было невозможно или достаточно сложно не реализовать, тогда количество возникающих проблем с поддержкой, вероятно, было бы большим, поскольку тонкие критические изменения были бы потеряны для многих пользователей.
- Это очень легко обойти.
В вашем примере так:
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
лямбды, разрешающиеawait
внутри в C # 5.0, мне было бы интересно узнать, почему они до сих пор не реализовали анонимные итераторы сyield
внутри. Более-менее, это тот же генератор конечных автоматов.