1. 程式人生 > >C#中foreach的實現原理

C#中foreach的實現原理

在探討foreach如何內部如何實現這個問題之前,我們需要理解兩個C#裡邊的介面,IEnumerable IEnumerator. C#裡邊的遍歷集合時用到的相關類中,IEnumerable是最基本的介面。這是一個可以進行泛型化的介面,比如說IEnumerable<User>.在微軟的.NET推出了這兩個介面後,才有了foreach的用法,可以說,foreach是建立在這兩個介面的基礎之上的,foreach的前提是其裡邊的容器要實現了IEnumerable介面。

IEnumerable這個接口裡邊定義的內容非常簡單,最重要的就是裡邊有一個抽象方法GetEnumerator. IEnumerable

的意思是這個集合是可以遍歷的,而這個GetEnumerator方法返回的IEnumerator的就是一個遍歷器,用這個工具來遍歷這個類。如果說IEnumerable 是一瓶香檳,那麼IEnumerator就是一個開瓶器。在實現這個IEnumerable介面的時候,必須要實現這個GetEnumerator方法,要返回一個例項化的IEnumorator. 

下面來介紹一下這個IEnumorator介面。這個介面中定義的內容也很簡單,包括Current,就是返回這個遍歷工具所指向的那個容器的當前的元素,MoveNext 方法就是指向下一個元素,當遍歷到最後沒有元素時,返回一個false.當我們實現一個

IEnumerable類的時候,我們的目的就應該是遍歷這個集合,所以同時我們要實現IEnumerator這個工具類,定義我們自己的邏輯來告訴CLR我們怎麼去遍歷這個集合。 

下面是一個簡單的例子,來說明一下這兩個介面的用法。

  1. // Person類包括兩個屬性。
  2. publicclass Person  
  3. {  
  4.     public Person(string fName, string lName)  
  5.     {  
  6.         this.firstName = fName;  
  7.         this.lastName = lName;  
  8.     }  
  9.     public
    string firstName;  
  10.     publicstring lastName;  
  11. }  
  12. //People類就是Person的集合,裡邊使用了一個數組把單個的物件存在這個陣列當中。而且因為實現了
  13. //IEnumerable介面,所以要實現GetEnumerator方法,返回一個實現了IEnumerator的類。
  14. publicclass People : IEnumerable  
  15. {  
  16.     private Person[] _people;  
  17.     public People(Person[] pArray)  
  18.     {  
  19.         _people = new Person[pArray.Length];  
  20.         for (int i = 0; i < pArray.Length; i++)  
  21.         {  
  22.             _people[i] = pArray[i];  
  23.         }  
  24.     }  
  25.     public PeopleEnum GetEnumerator()  
  26.     {  
  27.         returnnew PeopleEnum(_people);  
  28.     }  
  29. }  
  30. // 這裡我們需要定義一套邏輯去遍歷上邊的集合。
  31. publicclass PeopleEnum : IEnumerator  
  32. {  
  33.     public Person[] _people;  
  34.     int position = -1;  
  35.     public PeopleEnum(Person[] list)  
  36.     {  
  37.         _people = list;  
  38.     }  
  39.     publicbool MoveNext()  
  40.     {  
  41.         position++;  
  42.         return (position < _people.Length);  
  43.     }  
  44.     publicvoid Reset()  
  45.     {  
  46.         position = -1;  
  47.     }  
  48.     object IEnumerator.Current  
  49.     {  
  50.         get
  51.         {  
  52.             return Current;  
  53.         }  
  54.     }  
  55.     public Person Current  
  56.     {  
  57.         get
  58.         {  
  59.             try
  60.             {  
  61.                 return _people[position];  
  62.             }  
  63.             catch (IndexOutOfRangeException)  
  64.             {  
  65.                 thrownew InvalidOperationException();  
  66.             }  
  67.         }  
  68.     }  
  69. }  
  70. class App  
  71. {  
  72.     staticvoid Main()  
  73.     {  
  74.         Person[] peopleArray = new Person[3]  
  75.         {  
  76.             new Person("John""Smith"),  
  77.             new Person("Jim""Johnson"),  
  78.             new Person("Sue""Rabon"),  
  79.         };  
  80.         People peopleList = new People(peopleArray);  
  81.         //在這裡使用foreach就可以遍歷這個集合了,因為它實現了IEnumerable介面。
  82.         foreach (Person p in peopleList)  
  83.             Console.WriteLine(p.firstName + " " + p.lastName);  
  84.      }  
  85. }  
  86. /* This code produces output similar to the following: 
  87.  * 
  88.  * John Smith 
  89.  * Jim Johnson 
  90.  * Sue Rabon 
  91.  * 
  92.  */

如果說,foreach後臺的邏輯是這麼實現的?大概是這個樣子的。上邊的程式碼會被CLR翻譯成這樣。
  1. foreach (Person p in peopleList)  
  2.             Console.WriteLine(p.firstName + " " + p.lastName);  
  3. //翻譯成
  4. IEnumerator enumerator = (peopleList).GetEnumerator();  
  5. try {  
  6.       while (enumerator.MoveNext()) {  
  7.       Person element; //post C# 5
  8.       element = (Person )enumerator.Current;  
  9.     //下邊這句就是原來foreach方法體中的邏輯
  10.       Console.WriteLine(p.firstName + " " + p.lastName);  
  11.    }  
  12. }  
  13. finally {  
  14.    IDisposable disposable = enumerator as System.IDisposable;  
  15.    if (disposable != null) disposable.Dispose();  
  16. }  

附加:關於IEnumerable與ORM框架聯合使用時候的延遲載入問題,以及Resharper對於此介面mutiple enumeration警告問題

使用IEnumerable的時候,Resharper經常會提示這個問題?這個問題意思是,這個集合物件可能會返回不同的遍歷結果。

因為IEnumerable另外一個功能就是存放SQL一類的查詢邏輯,注意,這裡指的是查詢邏輯,而不是真正的查詢結果,也就是延遲載入。以下邊的例子為例,可能objects中存放的是SQL查詢邏輯。當第一次呼叫Any()方法的時候,會呼叫SQL語句查詢到資料庫中的結果,這時候是有一條資料的。但是objects呼叫First()方法來獲取這個記錄的時候,可能這時候的資料庫已經被其他的程式改了,沒有資料了,這時候就出現了dirty data的問題。所以,為了資料的一致性,需要在使用IEnumeralbe的時候,呼叫.ToList()方法把這些內容存放在一個個實實在在的容器中,這樣就前後一致了。

還有就是從程式碼效能角度考慮,每次都呼叫一下資料庫會很慢,所以乾脆一下全部把資料庫中符合條件的結果放到記憶體List中,用的時候直接從記憶體中拿就快多了。

  1. public List<object> Foo(IEnumerable<object> objects)  
  2. {  
  3.     if(objects == null || !objects.Any())  
  4.         thrownew ArgumentException();  
  5. var firstObject = objects.First();  
  6.     var list= DoSomeThing(firstObject);          
  7.     var secondList = DoSomeThingElse(objects);  
  8.     list.AddRange(secondList);  
  9. return list;  
  10. }  
 IEnumerable在一些ORM框架中實現了延遲載入的功能。比如說,在框架自己定義的容器物件中,實現了特定的IEnumerator介面,在MoveNext中指定邏輯,連線資料庫,獲取物件等。