C# 傳統遍歷與迭代器
引言:
在C# 1.0中我們經常使用foreach來遍歷一個集合中的元素,然而一個型別要能夠使用foreach關鍵字來對其進行遍歷必須實現IEnumerable或IEnumerable介面,(之所以來必須要實現IEnumerable這個介面,是因為foreach是迭代語句,要使用foreach必須要有一個迭代器才行的,然而IEnumerable介面中就有IEnumerator GetEnumerator()方法是返回迭代器的,所以實現了IEnumerable介面,就必須實現GetEnumerator()這個方法來返回迭代器,有了迭代器就自然就可以使用foreach語句了),然而在C# 1.0中要獲得迭代器就必須實現IEnumerable介面中的GetEnumerator()方法,然而要實現一個迭代器就必須實現IEnumerator介面中的bool MoveNext()和void Reset()方法,然而 C# 2.0中提供 yield關鍵字來簡化迭代器的實現,這樣在C# 2.0中如果我們要自定義一個迭代器就容易多了。下面就具體介紹了C# 2.0 中如何提供對迭代器的支援.
一、迭代器的介紹
迭代器大家可以想象成資料庫的遊標,即一個集合中的某個位置,C# 1.0中使用foreach語句實現了訪問迭代器的內建支援,使用foreach使我們遍歷集合更加容易(比使用for語句更加方便,並且也更加容易理解),foreach被編譯後會呼叫GetEnumerator來返回一個迭代器,也就是一個集合中的初始位置(foreach其實也相當於是一個語法糖,把複雜的生成程式碼工作交給編譯器去執行)。
二、C#1.0如何實現迭代器
在C# 1.0 中實現一個迭代器必須實現IEnumerator介面,下面程式碼演示了傳統方式來實現一個自定義的迭代器:
using System;
using System.Collections;
namespace 迭代器Demo
{
class Program
{
static void Main(string[] args)
{
Friends friendcollection = new Friends();
foreach (Friend f in friendcollection)
{
Console.WriteLine(f.Name);
}
Console.Read();
}
}
/// <summary>
/// 朋友類
/// </summary>
public class Friend
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
public Friend(string name)
{
this.name = name;
}
}
/// <summary>
/// 朋友集合
/// </summary>
public class Friends : IEnumerable
{
private Friend[] friendarray;
public Friends()
{
friendarray = new Friend[]
{
new Friend("張三"),
new Friend("李四"),
new Friend("王五")
};
}
// 索引器
public Friend this[int index]
{
get { return friendarray[index]; }
}
public int Count
{
get { return friendarray.Length; }
}
// 實現IEnumerable<T>介面方法
public IEnumerator GetEnumerator()
{
return new FriendIterator(this);
}
}
/// <summary>
/// 自定義迭代器,必須實現 IEnumerator介面
/// </summary>
public class FriendIterator : IEnumerator
{
private readonly Friends friends;
private int index;
private Friend current;
internal FriendIterator(Friends friendcollection)
{
this.friends = friendcollection;
index = 0;
}
#region 實現IEnumerator介面中的方法
public object Current
{
get
{
return this.current;
}
}
public bool MoveNext()
{
if (index + 1 > friends.Count)
{
return false;
}
else
{
this.current = friends[index];
index++;
return true;
}
}
public void Reset()
{
index = 0;
}
#endregion
}
}
執行結果:
三、使用C#2.0的新特性簡化迭代器的實現
在C# 1.0 中要實現一個迭代器必須實現IEnumerator介面,這樣就必須實現IEnumerator介面中的MoveNext、Reset方法和Current屬性,從上面程式碼中看出,為了實現FriendIterator迭代器需要寫40行程式碼,然而在C# 2.0 中通過yield return語句簡化了迭代器的實現,下面看看C# 2.0中簡化迭代器的程式碼:
using System;
using System.Collections;
namespace CSharp2._0版迭代器
{
class Program
{
#region C# 2.0 使用 yield return 語句實現迭代器
public class Car
{
public string BrandName { get; set; }
public Car() { }
public Car(string name)
{
this.BrandName = name;
}
}
public class Cars : IEnumerable
{
private Car[] carArray;
public Cars()
{
carArray = new Car[] {
new Car("Toyota"),
new Car("Nissna"),
new Car("Audi")
};
}
/// <summary>
/// 索引器
/// </summary>
public Car this[int index]
{
get { return carArray[index]; }
}
public int Count
{
get { return carArray.Length; }
}
// C# 2.0中簡化迭代器的實現
public IEnumerator GetEnumerator()
{
for (int index = 0; index < carArray.Length; index++)
{
// 使用yield return 就不需要額外定義一個類似FriendIterator的迭代器來實現IEnumerator
// 在C# 2.0中只需要使用下面語句就可以實現一個迭代器
yield return carArray[index];
}
}
}
#endregion
static void Main(string[] args)
{
#region C#2.0的新特性簡化迭代器的實現
Cars carsCollection = new Cars();
foreach (Car car in carsCollection)
{
Console.WriteLine(car.BrandName);
}
Console.ReadKey();
#endregion
}
}
}
在上面程式碼中有一個yield return 語句,這個語句的作用就是告訴編譯器GetEnumerator方法不是一個普通的方法,而是實現一個迭代器的方法,當編譯器看到yield return語句時,編譯器知道需要實現一個迭代器,所以編譯器生成中間程式碼時為我們生成了一個IEnumerator介面的物件,大家可以通過Reflector工具進行檢視:
從上面截圖可以看出,yield return 語句其實是C#中提供的另一個語法糖,簡化我們實現迭代器的原始碼,把具體實現複雜迭代器的過程交給編譯器幫我們去完成。
四、迭代器的執行過程
五、迭代器的延遲計算
從第四部分中迭代器的執行過程中可以知道迭代器是延遲計算的, 因為迭代的主體在MoveNext()中實現(因為在MoveNext()方法中訪問了集合中的當前位置的元素),Foreach中每次遍歷執行到in的時候才會呼叫MoveNext()方法,所以迭代器可以延遲計算,下面通過一個示例來演示迭代器的延遲計算:
using System;
using System.Collections.Generic;
namespace CSharp2._0迭代器的執行過程
{
class Program
{
#region 迭代器的延遲計算
public static IEnumerable<int> WithNoIterator()
{
List<int> list = new List<int>();
for (int i = 0; i < 5; i++)
{
Console.WriteLine("當前i的值為:{0}", i);
if (i > 1)
{
list.Add(i);
}
}
return list;
}
public static IEnumerable<int> WithIterator()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("在WithIterator方法中的, 當前i的值為:{0}", i);
if (i > 1)
{
yield return i;
}
}
}
#endregion
static void Main(string[] args)
{
// 測試一
Console.WriteLine("測試一:");
WithNoIterator();
Console.WriteLine();
// 測試二
Console.WriteLine("測試二:");
WithIterator();
Console.WriteLine();
// 測試三
Console.WriteLine("測試三:");
foreach (int j in WithIterator())
{
Console.WriteLine("在main輸出語句中,當前i的值為:{0}", j);
}
Console.ReadKey();
}
}
}
執行結果:
測試一:正如我們期望的那樣輸出了結果,列出來是為了更好說明測試二迭代器的延遲計算
測試二:什麼都沒有輸出,我們用Reflector工具檢視WithIterator()方法:
程式碼中呼叫WithIterator()時,對於編譯器而言,就是例項化了一個< WithIterator > d_1的物件(< WithIterator > d_1類是編譯看到WithIterator方法中包含Yield return 語句生成的一個迭代器類),所以執行測試一的程式碼時,控制檯中什麼都不輸出。
測試三:為什麼2,3,4會執行兩次的呢?下面具體為大家分析下為什麼會有這樣的結果。我們用Reflector工具檢視MoveNext():
從截圖中可以看到,將下面的輸出語句
Console.WriteLine(“在WithIterator方法中的, 當前i的值為:{0}”, i);
生成到迭代器的 MoveNext()方法體中了,所以
public static IEnumerable<int> WithIterator()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine("在WithIterator方法中的, 當前i的值為:{0}", i);
if (i > 1)
{
yield return i;
}
}
}
WithIterator方法中的值,被依次輸出到控制檯了。
參考 “迭代器的執行過程”圖中的示意,程式在執行到yield return i 關鍵字時才會返回下面的foreach迴圈,將i值帶回給j,輸出
Console.WriteLine("在main輸出語句中,當前i的值為:{0}", j);
後繼續執行for迴圈,所以 2,3,4被輸出到螢幕2次
六、總結
本文主要介紹了C# 2.0中通過yield return語句對迭代器實現的簡化,然而對於編譯器而言,卻沒有簡化,它同樣生成了一個類去實現IEnumerator介面,只是我們開發人員去實現一個迭代器得到了簡化而已。通過本文大家可以對迭代器有一個進一步的認識,並且迭代器的延遲計算也是Linq的基礎。
參考連結: