Eric Lippertは、イテレータブロックの制限(およびそれらの選択に影響を与える設計上の決定)に関する優れた一連の記事を書いています。
特に、イテレータブロックは、いくつかの高度なコンパイラコード変換によって実装されます。これらの変換は、無名関数またはラムダ内で発生する変換に影響を与え、特定の状況では、両方がコードを他の構成と互換性のない他の構成に「変換」しようとします。
結果として、彼らは相互作用を禁じられています。
イテレータブロックが内部でどのように機能するかは、ここでうまく処理されます。
非互換性の簡単な例として:
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;
}