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