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;
}
async
ラムダを許可await
できるようになったので、なぜyield
内部で匿名イテレータがまだ実装されていないのかを知りたいと思います。多かれ少なかれ、それは同じステートマシンジェネレータです。