多くの開発者は、C#の
foreachループが一見すると単純ではないことを知っていると思います。 まず、「
foreachコンストラクトを正常にコンパイルするには何が必要ですか?」という質問に答えましょう。 この質問に対する直感的な答えは、「クラスは
IEnumerableまたは
IEnumerable < T >を実装しています。」インターフェイスのようなもののようです。 しかし、これはそうではない、まあ、そうではない。
この質問に対する完全な答えは次のとおりです。「
foreachコンストラクトを正常にコンパイルするには、オブジェクトに
GetEnumerator ()メソッドが必要です。このメソッドは、
MoveNext ()メソッドと
Currentプロパティでオブジェクトを返します。および
IEnumerable < T > "。
この「アヒル」動作には2つの理由があります。
言語がシンプルで簡単で、ジェネリック、LINQ、またはその他のクロージャーが含まれていなかったC#1.0の昔を思い出しましょう。 しかし、ジェネリックがないため、「ジェネラライゼーション」と再利用はポリモーフィズムと
オブジェクトタイプに基づいており、実際にはコレクションとその反復子のクラスで行われました。
これらの同じイテレータは
IEnumerableと
IEnumeratorのペアであり、後者は
Currentプロパティで
オブジェクトを返しました。 その場合、
IEnumeratorインターフェイスを使用して、重要な型の強く型付けされたコレクションの要素を反復処理すると、各反復でこの値をパックおよびアンパックすることになります。これは、要素の列挙などの一般的な操作に関しては非常にコストがかかる可能性があります。
この問題を解決するために、アヒルのタイピングを使用したハックを使用し、パフォーマンスのためにOOPの原理を少し採点することにしました。 この場合、クラスは
IEnumerableインターフェイスを明示的に実装し、追加の
GetEnumerator ()メソッドを提供して、
Currentプロパティが特定の型(パッケージ化なしの
DateTimeなど
)を返す厳密に型指定された列挙子を返します。
わかった 恐竜を見つけましたが、現実の世界はどうですか? 確かに、まだ石器時代ではない中庭で、COMはすでにオークを与えており、ドンボックスはもはや本を書いていません。 現在、この動作の利点はありますか?
IEnumerable < T >および
IEnumerator < T >インターフェースの一般化バージョンが登場した後、アヒルのタイピングトリックは不要になったと思うかもしれませんが、これは完全に真実ではありません。
List < T >などのコレクションクラスをよく見ると、このクラスが(BCLの他のすべてのコレクションと同様に)
IEnumerable < T >インターフェイスを明示的に(明示的に)実装し、追加の
GetEnumerator ()メソッドを提供していることが
わかります :
はい、そうです。
GetEnumerator ()メソッドは、可変構造であるイテレータインスタンスを返します(結局、イテレータには現在のリストアイテムへの「ポインタ」が含まれます)。 また、多くの人によると、可変の重要なタイプは、.NETプラットフォームで最も鋭いものであり、非常に経験豊富な開発者でさえ足を引っ張ることができます。
注はい、はい。 私はすでに一般的な変数
列挙子と
特に変数有効型で耳を
すませていることを知っていますが、ここでは、この動作の理由などの説明を見つけようとします。 それでもう少し耐えてください:)
問題は、構造を反復子として使用し、
foreachループの 「アヒル」の性質と一緒に使用すると、この構造を使用するときにヒープ上のメモリ割り当てが妨げられることです。
var list = new List<int> {1, 2, 3};
最初の例では、「アヒル」の性質により、
Listクラスの
GetEnumerator ()メソッドが呼び出され、マネージヒープに追加のメモリ割り当てがなくても、スタック上で静かに生きる重要なタイプのオブジェクトを返します。 2番目のケースでは、変数
リストをインターフェイスにキャストします。これにより、インターフェイスメソッドが呼び出され、それに応じてイテレータがパッケージ化されます。 はい。C#開発者は、効率を向上させるために、ポリモーフィズムと他の多くのOOP原則を導入しています。
var list = new List {1、2、3};
var x1 = new { Items = ((IEnumerable<int>)list).GetEnumerator() }; while (x1.Items.MoveNext()) { Console.WriteLine(x1.Items.Current); } Console.ReadLine(); var x2 = new { Items = list.GetEnumerator() }; while (x2.Items.MoveNext()) { Console.WriteLine(x2.Items.Current); }
このため、最初の
whileループは期待される
1、2、3、2番目の
whileループを出力
します ...さて、自分でチェックしてください。
このようなソリューション(可変構造を使用)は、マイクロ最適化を超えているように見えますが、
foreachループをネストでき、ギガバイトのメモリを備えたマルチコアプロセッサで全員が動作するわけではないことを忘れないでください。 この決定を下す前に、BCLチームは、構造を使用することが本当に価値があることを示すいくつかの真剣な調査を行いました。
注独自のイテレータまたは他の補助クラスを実装するときに、この例をすぐに使用しないでください。 構造を使用すること自体が最適化されますが、可変構造を使用することは重大な決定であるため、セキュリティを犠牲にすることで得られる利点について明確にする必要があります。
小さな追加: Dispose 呼び出しの目的は何 ですか?
foreachループ実装のもう1つの機能は、イテレータの
Disposeメソッドを呼び出すことです。 以下は、
foreachループで
リスト変数を反復処理するときにコンパイラーによって生成されるコードの簡易バージョンです。
{ var enumerator = list.GetEnumerator(); try { while(enumerator.MoveNext()) { int current = enumerator.Current; Console.WriteLine(current); } } finally { enumerator.Dispose(); } }
反復子で管理対象リソースがどこから来るのかについて、合理的な疑問が生じる場合がありますか? ええ、はい、メモリ内のコレクションを並べ替える場合、実際にはどこから来るのかはわかりませんが、C#の列挙子はメモリ内のコレクションのイテレータとしてだけでなく使用できることを忘れないでください。 誰もファイルの内容を1行ずつ返すイテレータを気にしません。
public static class FileEx { public static IEnumerable<string> ReadByLine(string path) { if (path == null) throw new ArgumentNullException("path"); return ReadByLineImpl(path); } private static IEnumerable<string> ReadByLineImpl(string path) { using (var sr = new StreamReader(path)) { string s; while ((s = sr.ReadLine()) != null) yield return s; } } } foreach(var line in FileEx.ReadByLine("D:\\1.txt")) { Console.WriteLine(line); }
そのため、ファイルを開く
ReadByLineメソッドがありますが、これは確かにリソースであり、いつ閉じるのですか? 明らかに、
ReadByLineImplメソッドが
controlを離れるたびに毎回ではありません。それは、このファイルにある行と同じ回数だけ閉じるためです。
実際、ファイルは一度閉じられます。これは、イテレーターの
Disposeメソッドを呼び出すときと同じです。これは
foreachループの finallyブロックで発生し
ます 。 これは、
finallyブロックが自動的に呼び出されず、ハンドルによってのみ呼び出される場合の.NETプラットフォームでのまれなケースの1つです。 そのため、何らかのシーケンスを手動で突然反復する場合、イテレーターにリソースを含めることができることを忘れてはなりません。イテレーターの
Disposeメソッドを明示的に呼び出してそれらをクリアすることは非常に良いことです。
注C#のイテレータについては、注... C#のイテレータを参照してください
。Z.Y. そして、誰がすぐにこの質問に答えることができます:なぜ2つのメソッド
ReadByLineと
ReadByLineImplが必要なのですか、なぜ1つのメソッドのみを使用しないのですか?
Z.Y.Y. ちなみに、
foreachブロックはC#でのダックタイピングの唯一の例からはほど遠いですが、さらに多くの例を思い出せますか?