有效地處理數據時當今程序設計語言和框架的一個任務。.NET擁有一個精心構建的集合類系統,它利用迭代器的功能實現對數據的順序訪問。 惰性枚舉是一個迭代方法,其核心思想是只在需要的時候才去讀取數據。這個思想保證了任何迭代算法都十分有效,同時又可以靈活地根據需要讀取任意多的數據,而且不會造成過多的開銷。 C#函數式程序設計之枚舉元素 .NET集合類型的基礎是一個名為IEnumberable的接口,以下就是這個接口的聲明: public interface IEnumerable { IEnumerator GetEnumerator(); } 實際上IEnumberable接口只允許程序員做一件事:查詢類的另一個接口IEnumerator: public interface IEnumerator { object Current { get; } bool MoveNext(); void Reset(); } 下面這個類事實上是根據IEnumberable和IEnumerator接口聲明的迭代模式的最簡實現形式: public class EndlessListWithoutInterfaces { public EndlessListWithoutInterfaces GetEnumerator() { return this; } public bool NoveNext() { return true; } public object Current { get { return "Something"; } } } 在EndlessListWithoutInterfaces類中也可以使用C#的foreach結構: var list = new EndlessListWithoutInterfaces(); foreach (var item in list) { Console.WriteLine(item); } 此實現極其基本,因此這將導致一個無限循環。 下面是EndlessList類的實現,這個類使用IEnumerator和IEnumberable兩個類,這兩個類相互嵌套,盡管它們不需要裡外嵌套。但實際上,在IEnumberable類中嵌套IEnumerator類是一個常見模式: public class EndlessList:IEnumerable { public class Enumerator:IEnumerator { int val = -1; public object Current { get { return val; } } public bool NoveNext() { val++; return true; } public void Reset() { val = -1; } } public IEnumerator GetEnumerator() { return new Enumerator(); } } 值序列的這種實現模式是非常靈活的,而且功能也強大。 C#函數式程序設計之迭代器函數的實現 C#2.0版本中引入了迭代器。它允許程序員創建IEnumerator/IEnumberable組合的實現,而無須手動實現其中的任意一個接口。除了支持非泛型接口外,它還支持泛型接口,因此可以只實現IEnumerator。 通常為了應用這個功能,只需要定義一個返回某個類型值的函數。編譯器為了使用它的轉換操作而采用的第二個准則是在函數中至少使用幾個特殊關鍵字中的一個。最常用的是yield return語句。例如,前面的EndlessList例子可以由如下的一個C#迭代器實現: public static IEnumerable<int> EndlessListFunc() { int val = 0; while (true) yield return val++; } 此函數的返回類型是一個IEnumberable<int>,因此在原本使用一個能實現此接口的類實例的地方現在可以使用這個函數。下面幾個語句可以迭代訪問EndlessListFunc序列: var list = EndlessListFunc(); foreach (var item in list) { Console.WriteLine(item); } IEnumerator/IEnumberable兩個接口使用的整個聲明模式與惰性有關,即只是在需要時才讀取數據。這種模式的優點是:一個序列只是整個算法的一個很少但非常靈活的部分,序列也無需對它的將來使用進行任何假設。 下面這段代碼是一個迭代器的實際例子,利用Twitter流的一個搜索,從Web服務讀取數據。從Web服務讀取數據也采用惰性方法,因為它使用了自動分頁技術。迭代器根據需要讀取新頁,把長度未知的字符串序列返回給調用者。調用者按照自己喜歡的方式處理這個序列: private static void TwitterSearchIterator() { foreach (var tweet in GetTweets("#msdn").Take(10)) Console.WriteLine(tweet); } private static IEnumerable<string> GetTweets(string searchString) { var url = "http://search.twitter.com/search.atom?q="; int page = 1; var escapedSearchString = searchString.Replace("@", "%40").Replace("#", "%23"); XNamespace ns = "http://www.w3.org/2005/Atom"; while (true) { var doc = XDocument.Load(String.Format("{0}{1} & page={2}", url, escapedSearchString, page)); var entries = doc.Root.Elements(ns + "entry"); if (entries.Count() == 0) yield break; foreach (var entry in entries) yield return entry.Element(ns+"author").Element(ns+"name").Value+": "+WebUtility.HtmlDecode(entry.Element(ns+"title").Value; page++; } } 迭代器不僅可以返回前面例子使用的IEnumberable和IEnumberable<T>接口,還可以返回IEnumerator和IEnumerator<T>接口。 C#函數式程序設計之鏈式迭代器 很容易把函數形式的迭代器鏈接在一起,並通過它們創建復雜的處理流程。這個思想廣泛用於LINQ,這是函數式程序設計一個最重要的概念。 前面介紹了EndlessListFunc迭代器,它可以返回一個無窮的整數值序列,處理序列的迭代器函數可以與這個迭代器一起使用。例如下例是Take函數的實現,它通過參數接受一個證書序列和一個計數值,且只返回源序列中的前幾個元素: public static IEnumerable<int> Take(int count, IEnumerable<int> source) { int used = 0; foreach (var item in source) if (count > used++) yield return item; else yield break; } 可以把Take與EndlessListFunc一起使用: var FiveElements = Take(5, EndlessListFunc()); foreach (var item in FiveElements) Console.WriteLine(item); 這個例子很好地說明了如何把迭代器當成模塊使用。 用在鏈式迭代器中的第二種類型函數是使用實際內容的函數,比如執行數值計算。