.NET中的迭代器
迭代器
在C#中,foreach語句使得能夠進行比for循環語句更直接和簡單的對集合的迭代。.NET中迭代器是通過IEnumerable
和IEnumerator
接口來實現的(當然,這兩個接口還有其對應的泛型版本:IEnumerable<T>
和 IEnumerator<T>
)。其源代碼如下(部分代碼省略)
public interface IEnumerable
{
// Interfaces are not serializable
// Returns an IEnumerator for this enumerable Object. The enumerator provides
// a simple way to access all the contents of a collection.
IEnumerator GetEnumerator();
}
public interface IEnumerator
{
// Interfaces are not serializable
// Advances the enumerator to the next element of the enumeration and
// returns a boolean indicating whether an element is available. Upon
// creation, an enumerator is conceptually positioned before the first
// element of the enumeration, and the first call to MoveNext
// brings the first element of the enumeration into view.
//
bool MoveNext();
// Returns the current element of the enumeration. The returned value is
// undefined before the first call to MoveNext and following a
// call to MoveNext that returned false. Multiple calls to
// GetCurrent with no intervening calls to MoveNext
// will return the same object.
//
Object Current {
get;
}
// Resets the enumerator to the beginning of the enumeration, starting over.
// The preferred behavior for Reset is to return the exact same enumeration.
// This means if you modify the underlying collection then call Reset, your
// IEnumerator will be invalid, just as it would have been if you had called
// MoveNext or Current.
//
void Reset();
}
一個基礎例子
以下兩個代碼片段(生成從 0 到 9 的整數序列)等效,我們在 C#
中使用 yield return
上下文關鍵字定義迭代器方法。
private static IEnumerable<int> GetSingleDigitNumbers()
{
yield return 0;
yield return 1;
yield return 2;
yield return 3;
yield return 4;
yield return 5;
yield return 6;
yield return 7;
yield return 8;
yield return 9;
}
private static IEnumerable<int> GetSingleDigitNumbers()
{
int index = 0;
while (index++ < 10)
yield return index - 1;
}
讓我們自定義的數據類型實現迭代器
public class MyIEnumerable : IEnumerable
{
private string[] _strList;
public MyIEnumerable(string[] strList)
{
_strList = strList;
}
public IEnumerator GetEnumerator()
{
//return new MyIEnumerator(_strList);
for (int i = 0; i < _strList.Length; i++)
{
yield return _strList[i];
}
}
}
public class MyIEnumerator : IEnumerator
{
private string[] _strList;
private int position;
public MyIEnumerator(string[] strList)
{
_strList = strList;
position = -1;
}
public object Current
{
get { return _strList[position]; }
}
public bool MoveNext()
{
position++;
if (position < _strList.Length)
return true;
return false;
}
public void Reset()
{
position = -1;
}
}
調用
public static void RunMyIEnumerable()
{
string[] strList = new string[] { "第一個節點數據", "第二個節點數據", "第三個節點數據" };
MyIEnumerable myIEnumerable = new MyIEnumerable(strList);
// 1.獲取IEnumerator接口實例
var enumerator = myIEnumerable.GetEnumerator();
// 2.判斷是否可以繼續循環
while (enumerator.MoveNext())
{
// 3.取值
Console.WriteLine(enumerator.Current);
}
Console.WriteLine("==========");
foreach (var item in myIEnumerable) // 效果等同於上述方式
{
Console.WriteLine(item);
}
}
我們調用GetEnumerator的時候,看似裏面for循環了一次,其實這個時候沒有做任何操作。只有調用MoveNext的時候纔會對應調用for循環。
這也是爲什麼Linq to Object中要返回IEnumerable?
因爲IEnumerable是延遲加載的,每次訪問的時候才取值。也就是我們在Lambda裏面寫的where、select並沒有循環遍歷(只是在組裝條件),只有在ToList或foreache的時候才真正去集合取值了。這樣大大提高了性能。
以上引用自農碼一生的博客先說IEnumerable,我們每天用的foreach你真的懂它嗎?
參考
本文引用以下文章