Foreachがどのように実現されるかについて

4780 ワード

foreachがどのように内部でこの問題を実現するかを検討する前に、2つのC#の中のインタフェース、IEnumerableとIEnumeratorを理解する必要があります.C#内のループセットで使用される関連クラスでは、IEnumerableが最も基本的なインタフェースです.これはIEnumerableのような汎用化可能なインタフェースです.マイクロソフトのNETがこの2つのインタフェースを発売してから、foreachの使い方ができました.foreachはこの2つのインタフェースの基礎の上に構築されていると言えます.foreachの前提は、その中の容器がIEnumerableインタフェースを実現することです.
 
IEnumerableというインタフェースで定義されている内容は非常に簡単で、最も重要なのは抽象的な方法があることです.IEnumerableとは、この集合が遍歴可能であることを意味し、このGetEnumeratorメソッドが返すIEnumeratorは、このツールを使用してクラスを遍歴する遍歴器である.IEnumerableがシャンパンなら、IEnumeratorは開瓶器です.このIEnumerableインタフェースを実装するには、このGetEnumeratorメソッドを実装し、インスタンス化されたIEnumoratorを返さなければならない. 
このIEnumoratorインタフェースをご紹介します.このインタフェースで定義する内容も簡単で、Currentを含めて、この遍歴ツールが指すコンテナの現在の要素を返します.MoveNextメソッドは次の要素を指し、最後に要素がないまで遍歴するとfalseを返します.IEnumerableクラスを実現するとき、私たちの目的はこの集合を遍歴することです.だから、同時にIEnumeratorというツールクラスを実現し、CLRにこの集合を遍歴する方法を教えるために、私たちの論理を定義します. 
次は簡単な例で、この2つのインタフェースの使い方を説明します.
// Person       。
public class Person
{
    public Person(string fName, string lName)
    {
        this.firstName = fName;
        this.lastName = lName;
    }
    public string firstName;
    public string lastName;
}
//People   Person   ,                       。       
//IEnumerable  ,     GetEnumerator  ,       IEnumerator  。
public class People : IEnumerable
{
    private Person[] _people;
    public People(Person[] pArray)
    {
        _people = new Person[pArray.Length];
        for (int i = 0; i < pArray.Length; i++)
        {
            _people[i] = pArray[i];
        }
    }
    public PeopleEnum GetEnumerator()
    {
        return new PeopleEnum(_people);
    }
}
//                     。
public class PeopleEnum : IEnumerator
{
    public Person[] _people;

    int position = -1;
    public PeopleEnum(Person[] list)
    {
        _people = list;
    }
    public bool MoveNext()
    {
        position++;
        return (position < _people.Length);
    }
    public void Reset()
    {
        position = -1;
    }
    object IEnumerator.Current
    {
        get
        {
            return Current;
        }
    }
    public Person Current
    {
        get
        {
            try
            {
                return _people[position];
            }
            catch (IndexOutOfRangeException)
            {
                throw new InvalidOperationException();
            }
        }
    }
}
class App
{
    static void Main()
    {
        Person[] peopleArray = new Person[3]
        {
            new Person("John", "Smith"),
            new Person("Jim", "Johnson"),
            new Person("Sue", "Rabon"),
        };
        People peopleList = new People(peopleArray);

		//     foreach          ,      IEnumerable  。
        foreach (Person p in peopleList)
            Console.WriteLine(p.firstName + " " + p.lastName);
     }
}
/* This code produces output similar to the following:
 *
 * John Smith
 * Jim Johnson
 * Sue Rabon
 *
 */

もしforeachバックグラウンドの論理がこのように実現されたとしたら?多分こんな感じです.上のコードはCLRによってこのように翻訳されます.
foreach (Person p in peopleList)
            Console.WriteLine(p.firstName + " " + p.lastName);
//   

IEnumerator enumerator = (peopleList).GetEnumerator();
try {
      while (enumerator.MoveNext()) {
      Person element; //post C# 5
      element = (Person )enumerator.Current;
	//        foreach       
      Console.WriteLine(p.firstName + " " + p.lastName);
   }
}
finally {
   IDisposable disposable = enumerator as System.IDisposable;
   if (disposable != null) disposable.Dispose();
}

追加:IEnumerableとORMフレームワークの併用時の遅延ロードの問題、およびResharperがこのインタフェースmutiple enumerationに警告する問題について
IEnumerableを使うとき、Resharperはよくこの問題を提示しますか?この問題は、この集合オブジェクトが異なるループ結果を返す可能性があることを意味します.
IEnumerableのもう一つの機能はSQLクラスのクエリーロジックを格納することであるため、ここでは本当のクエリー結果、つまり遅延ロードではなくクエリーロジックを指すことに注意してください.次のエッジの例を例に挙げると、objectsにSQLクエリーロジックが格納されている可能性があります.Any()メソッドが最初に呼び出されると、SQL文がデータベースにクエリされた結果が呼び出され、データが表示されます.しかしobjectsがFirst()メソッドを呼び出してこのレコードを取得した場合、この時点でデータベースが他のプログラムに変更され、データがない場合、dirty dataの問題が発生する可能性があります.したがって、データの整合性のためには、IEnumeralbeを使用する際に呼び出す必要がある.ToList()メソッドは,これらの内容を個々の実在する容器に格納することで,前後が一致する. 
また、コード性能の観点から、毎回データベースを呼び出すのは遅いので、いっそすべてのデータベースの条件に合った結果をメモリリストに入れて、使うときに直接メモリから取るとずっと速くなります.
public List Foo(IEnumerable objects)
{
    if(objects == null || !objects.Any())
        throw new ArgumentException();
var firstObject = objects.First();
    var list= DoSomeThing(firstObject);        
    var secondList = DoSomeThingElse(objects);
    list.AddRange(secondList);
return list;
}
 

 IEnumerable
いくつかの
ORM
フレームワークでは、遅延ロード機能が実現されています.たとえば,フレームが独自に定義したコンテナオブジェクトでは,特定の
IEnumerator
インタフェース
M
oveNext
で指定します.