C#中foreach的實現原理
在探討foreach如何內部如何實現這個問題之前,我們需要理解兩個C#裡邊的介面,IEnumerable與 IEnumerator. 在C#裡邊的遍歷集合時用到的相關類中,IEnumerable是最基本的介面。這是一個可以進行泛型化的介面,比如說IEnumerable<User>.在微軟的.NET推出了這兩個介面後,才有了foreach的用法,可以說,foreach是建立在這兩個介面的基礎之上的,foreach的前提是其裡邊的容器要實現了IEnumerable介面。
IEnumerable這個接口裡邊定義的內容非常簡單,最重要的就是裡邊有一個抽象方法GetEnumerator. IEnumerable
下面來介紹一下這個IEnumorator介面。這個介面中定義的內容也很簡單,包括Current,就是返回這個遍歷工具所指向的那個容器的當前的元素,MoveNext
方法就是指向下一個元素,當遍歷到最後沒有元素時,返回一個false.當我們實現一個
下面是一個簡單的例子,來說明一下這兩個介面的用法。
- // Person類包括兩個屬性。
- publicclass Person
- {
- public Person(string fName, string lName)
- {
- this.firstName = fName;
- this.lastName = lName;
- }
-
public
- publicstring lastName;
- }
- //People類就是Person的集合,裡邊使用了一個數組把單個的物件存在這個陣列當中。而且因為實現了
- //IEnumerable介面,所以要實現GetEnumerator方法,返回一個實現了IEnumerator的類。
- publicclass 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()
- {
- returnnew PeopleEnum(_people);
- }
- }
- // 這裡我們需要定義一套邏輯去遍歷上邊的集合。
- publicclass PeopleEnum : IEnumerator
- {
- public Person[] _people;
- int position = -1;
- public PeopleEnum(Person[] list)
- {
- _people = list;
- }
- publicbool MoveNext()
- {
- position++;
- return (position < _people.Length);
- }
- publicvoid Reset()
- {
- position = -1;
- }
- object IEnumerator.Current
- {
- get
- {
- return Current;
- }
- }
- public Person Current
- {
- get
- {
- try
- {
- return _people[position];
- }
- catch (IndexOutOfRangeException)
- {
- thrownew InvalidOperationException();
- }
- }
- }
- }
- class App
- {
- staticvoid 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()方法把這些內容存放在一個個實實在在的容器中,這樣就前後一致了。
還有就是從程式碼效能角度考慮,每次都呼叫一下資料庫會很慢,所以乾脆一下全部把資料庫中符合條件的結果放到記憶體List中,用的時候直接從記憶體中拿就快多了。
- public List<object> Foo(IEnumerable<object> objects)
- {
- if(objects == null || !objects.Any())
- thrownew ArgumentException();
- var firstObject = objects.First();
- var list= DoSomeThing(firstObject);
- var secondList = DoSomeThingElse(objects);
- list.AddRange(secondList);
- return list;
- }