迭代器模式(Iterator Pattern)
1、迭代器模式是設計模式中行為型模式(behavioral pattern)的一個例子,他是一種簡化物件間通訊的模式,也是一種非常容易理解和使用的模式,簡單來說,迭代器模式使得你能夠獲取到序列中的所有元素而不用關心是其型別是array,list,linked list或者是其他什么序列結構,這一點使得能夠非常高效的構建資料處理通道(data pipeline)--即資料能夠進入處理通道,進行一系列的變換,或者過濾,然后得到結果,事實上,這正是Linq的核心模式,
2、在.NET中,迭代器模式被IEnumerator和IEnumerable及其對應的泛型介面所封裝,如果一個類實作了IEnumerable介面,那么就能夠被迭代;呼叫GetEnumerator方法將回傳IEnumerator介面的實作,它就是迭代器本身,迭代器類似資料庫中的游標,他是資料序列中的一個位置記錄,迭代器只能向前移動,同一資料序列中可以有多個迭代器同時對資料進行操作,
3、含有yield的函式說明它是一個生成器,而不是普通的函式,當程式運行到yield這一行時,該函式會回傳值,并保存當前域的所有變數狀態;等到該函式下一次被呼叫時,會從上一次中斷的地方開始執行,一直遇到下一個yield,程式回傳值,并在此保存當前狀態; 如此反復,直到函式正常執行完成,
4、yield是語法糖,編譯時由編譯器生成Iterrator的代碼,包括MoveNext、Current、Reset等,
一、迭代器模式的實作原理
首先我們先來看個例子:
/// <summary> /// 食物 /// </summary> public class Food { public int Id { get; set; } public string Name { get; set; } public int Price { get; set; } } /// <summary> /// 肯德基選單 /// </summary> public class KFCMenu { private Food[] _foodList = new Food[3]; public KFCMenu() { this._foodList[0] = new Food() { Id = 1, Name = "漢堡包", Price = 15 }; this._foodList[1] = new Food() { Id = 2, Name = "可樂", Price = 10 }; this._foodList[2] = new Food() { Id = 3, Name = "薯條", Price = 8 }; } public Food[] GetFoods() { return this._foodList; } } /// <summary> /// 麥當勞選單 /// </summary> public class MacDonaldMenu { private List<Food> _foodList = new List<Food>(); public MacDonaldMenu() { this._foodList.Add(new Food() { Id = 1, Name = "雞肉卷", Price = 15 }); this._foodList.Add(new Food() { Id = 2, Name = "紅豆派", Price = 10 }); this._foodList.Add(new Food() { Id = 3, Name = "薯條", Price = 9 }); } public List<Food> GetFoods() { return this._foodList; } }
class Program { static void Main(string[] args) { { KFCMenu kfcMenu = new KFCMenu(); Food[] foodCollection = kfcMenu.GetFoods(); for (int i = 0; i < foodCollection.Length; i++) { Console.WriteLine("KFC: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price); } } { MacDonaldMenu macDonaldMenu = new MacDonaldMenu(); List<Food> foodCollection = macDonaldMenu.GetFoods(); for (int i = 0; i < foodCollection.Count(); i++) { Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price); } } Console.ReadKey(); } }
從上面的例子可以發現肯德基選單和麥當勞選單差不多,但是呢一個是陣列存放一個是集合存放,這就導致了它們兩者的訪問方式不太一樣,
那么從某種角度上看我們當然希望它們兩者能有一個統一的訪問方式,
class Program { static void Main(string[] args) { { KFCMenu kfcMenu = new KFCMenu(); Food[] foodCollection = kfcMenu.GetFoods(); for (int i = 0; i < foodCollection.Length; i++) { Console.WriteLine("KFC: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price); } foreach (var item in foodCollection) { Console.WriteLine("KFC: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price); } } { MacDonaldMenu macDonaldMenu = new MacDonaldMenu(); List<Food> foodCollection = macDonaldMenu.GetFoods(); for (int i = 0; i < foodCollection.Count(); i++) { Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price); } foreach (var item in foodCollection) { Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price); } } Console.ReadKey(); } }
可以發現使用foreach后它們兩者的訪問方式就統一了,那么這個foreach是怎么設計出來的呢?其實這就用到了迭代器,迭代器可以為不同的資料結構提供一個通用的訪問方式,
下面我們直接通過代碼來看下迭代器的實作原理:
/// <summary> /// 迭代器抽象類(模擬IEnumerator) /// </summary> public interface IIterator<T> { /// <summary> /// 當前的物件 /// </summary> T Current { get; } /// <summary> /// 移動到下一個物件,是否存在, /// </summary> /// <returns></returns> bool MoveNext(); /// <summary> /// 重置 /// </summary> void Reset(); } /// <summary> /// 抽象聚合類(模擬IEnumerable) /// </summary> public interface IAggregate<T> { IIterator<T> GetEnumerator(); }
/// <summary> /// 迭代器具體類 /// 肯德基選單迭代器 /// </summary> public class KFCMenuIterator : IIterator<Food> { private Food[] _foodList = null; public KFCMenuIterator(KFCMenu kfcMenu) { this._foodList = kfcMenu.GetFoods(); } private int _currentIndex = -1; public Food Current { get { return this._foodList[_currentIndex]; } } public bool MoveNext() { return this._foodList.Length > ++this._currentIndex; //此處判斷方式是.Length } public void Reset() { this._currentIndex = -1; } } /// <summary> /// 迭代器具體類 /// 麥當勞選單迭代器 /// </summary> public class MacDonaldIterator : IIterator<Food> { private List<Food> _foodList = null; public MacDonaldIterator(MacDonaldMenu macDonaldMenu) { this._foodList = macDonaldMenu.GetFoods(); } private int _currentIndex = -1; public Food Current { get { return this._foodList[_currentIndex]; } } public bool MoveNext() { return this._foodList.Count > ++this._currentIndex; //此處判斷方式是.Count } public void Reset() { this._currentIndex = -1; } }
/// <summary> /// 肯德基選單 /// 實作IAggregate /// </summary> public class KFCMenu : IAggregate<Food> { private Food[] _foodList = new Food[3]; public KFCMenu() { this._foodList[0] = new Food() { Id = 1, Name = "漢堡包", Price = 15 }; this._foodList[1] = new Food() { Id = 2, Name = "可樂", Price = 10 }; this._foodList[2] = new Food() { Id = 3, Name = "薯條", Price = 8 }; } public Food[] GetFoods() { return this._foodList; } public IIterator<Food> GetEnumerator() { return new KFCMenuIterator(this); } } /// <summary> /// 麥當勞選單 /// 實作IAggregate /// </summary> public class MacDonaldMenu : IAggregate<Food> { private List<Food> _foodList = new List<Food>(); public MacDonaldMenu() { this._foodList.Add(new Food() { Id = 1, Name = "雞肉卷", Price = 15 }); this._foodList.Add(new Food() { Id = 2, Name = "紅豆派", Price = 10 }); this._foodList.Add(new Food() { Id = 3, Name = "薯條", Price = 9 }); } public List<Food> GetFoods() { return this._foodList; } public IIterator<Food> GetEnumerator() { return new MacDonaldIterator(this); } }
使用如下(紅色字體部分):
using System; using System.Collections.Generic; using System.Linq; using IteratorPattern.Iterator; using IteratorPattern.Menu; namespace IteratorPattern { /// <summary> /// 迭代器模式(yield return) /// 1、迭代器模式是設計模式中行為型模式(behavioral pattern)的一個例子,他是一種簡化物件間通訊的模式,也是一種非常容易理解和使用的模式, /// 簡單來說,迭代器模式使得你能夠獲取到序列中的所有元素而不用關心是其型別是array,list,linked list或者是其他什么序列結構, /// 這一點使得能夠非常高效的構建資料處理通道(data pipeline)--即資料能夠進入處理通道,進行一系列的變換,或者過濾,然后得到結果, /// 事實上,這正是LINQ的核心模式,Linq to object的延遲查詢,按需獲取, /// 2、在.NET中,迭代器模式被IEnumerator和IEnumerable及其對應的泛型介面所封裝,如果一個類實作了IEnumerable介面,那么就能夠被迭代; /// 呼叫GetEnumerator方法將回傳IEnumerator介面的實作,它就是迭代器本身,迭代器類似資料庫中的游標,他是資料序列中的一個位置記錄, /// 迭代器只能向前移動,同一資料序列中可以有多個迭代器同時對資料進行操作, /// 3、含有yield的函式說明它是一個生成器,而不是普通的函式,當程式運行到yield這一行時,該函式會回傳值,并保存當前域的所有變數狀態; /// 等到該函式下一次被呼叫時,會從上一次中斷的地方開始執行,一直遇到下一個yield,程式回傳值,并在此保存當前狀態; 如此反復,直到函式正常執行完成, /// 4、yield是語法糖,編譯時由編譯器生成Iterrator的代碼,包括MoveNext、Current、Reset等, /// </summary> class Program { static void Main(string[] args) { { KFCMenu kfcMenu = new KFCMenu(); Food[] foodCollection = kfcMenu.GetFoods(); for (int i = 0; i < foodCollection.Length; i++) { Console.WriteLine("KFC: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price); } foreach (var item in foodCollection) { Console.WriteLine("KFC: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price); } IIterator<Food> foodIterator = kfcMenu.GetEnumerator(); while (foodIterator.MoveNext()) { Food food = foodIterator.Current; Console.WriteLine("KFC: Id={0} Name={1} Price={2}", food.Id, food.Name, food.Price); } } { MacDonaldMenu macDonaldMenu = new MacDonaldMenu(); List<Food> foodCollection = macDonaldMenu.GetFoods(); for (int i = 0; i < foodCollection.Count(); i++) { Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", foodCollection[i].Id, foodCollection[i].Name, foodCollection[i].Price); } foreach (var item in foodCollection) { Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", item.Id, item.Name, item.Price); } IIterator<Food> foodIterator = macDonaldMenu.GetEnumerator(); while (foodIterator.MoveNext()) { Food food = foodIterator.Current; Console.WriteLine("MacDonald: Id={0} Name={1} Price={2}", food.Id, food.Name, food.Price); } } Console.ReadKey(); } } }
可以發現使用迭代器模式后我們做到了兩者訪問方式的統一,
在C# 1.0中我們經常使用foreach來遍歷一個集合中的元素,然而一個型別要能夠使用foreach關鍵字來對其進行遍歷必須實作IEnumerable或IEnumerable<T>介面,
之所以必須要實作IEnumerable這個介面,是因為foreach是迭代陳述句,要使用foreach就必須要有一個迭代器才行,
而IEnumerable介面中就有IEnumerator GetEnumerator()方法是回傳迭代器的,實作了IEnumerable介面就必須實作GetEnumerator()這個方法來回傳迭代器,有了迭代器自然就可以使用foreach陳述句了,
在C# 1.0中要實作一個迭代器就必須實作IEnumerator介面中的bool MoveNext()和void Reset()方法,
而在C# 2.0中提供了yield關鍵字來簡化迭代器的實作,這樣在C# 2.0中如果我們要自定義一個迭代器就容易多了,
二、在C#1.0中實作迭代器
在C# 1.0 中實作一個迭代器必須實作IEnumerator介面,下面代碼演示了傳統方式來實作一個自定義的迭代器:
using System; namespace IteratorPattern.IteratorImpl { /// <summary> /// 朋友類 /// </summary> public class Friend { private string _name; public string Name { get => _name; set => _name = value; } public Friend(string name) { this._name = name; } } }
using System.Collections; namespace IteratorPattern.IteratorImpl.Demo1 { /// <summary> /// 自定義迭代器,必須實作IEnumerator介面 /// </summary> public class FriendIterator : IEnumerator { private readonly Friends _friends; private int _index; private Friend _current; internal FriendIterator(Friends friends) { this._friends = friends; _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 實作IEnumerator介面中的方法 } }
using System.Collections; namespace IteratorPattern.IteratorImpl.Demo1 { /// <summary> /// 朋友集合 /// </summary> public class Friends : IEnumerable { private Friend[] _arrFriend; public Friends() { _arrFriend = new Friend[] { new Friend("張三"), new Friend("李四"), new Friend("王五") }; } /// <summary> /// 索引器 /// </summary> public Friend this[int index] { get { return _arrFriend[index]; } } public int Count { get { return _arrFriend.Length; } } /// <summary> /// 實作IEnumerable介面方法 /// </summary> public IEnumerator GetEnumerator() { return new FriendIterator(this); } } }
使用foreach方式遍歷如下所示:
//在C#1.0中實作迭代器 { Console.WriteLine("在C#1.0中實作迭代器"); var friendCollection = new IteratorImpl.Demo1.Friends(); foreach (Friend item in friendCollection) { Console.WriteLine(item.Name); } }
運行結果如下:

三、在C#2.0中實作迭代器
在C# 1.0 中要實作一個迭代器需要實作IEnumerator介面,這樣就必須實作IEnumerator介面中的MoveNext、Reset方法和Current屬性,而在C# 2.0 中通過yield return陳述句簡化了迭代器的實作,
下面來看看C# 2.0中簡化迭代器的寫法:
using System; namespace IteratorPattern.IteratorImpl { /// <summary> /// 朋友類 /// </summary> public class Friend { private string _name; public string Name { get => _name; set => _name = value; } public Friend(string name) { this._name = name; } } }
using System.Collections; namespace IteratorPattern.IteratorImpl.Demo2 { /// <summary> /// 朋友集合 /// </summary> public class Friends : IEnumerable { private Friend[] _arrFriend; public Friends() { _arrFriend = new Friend[] { new Friend("張三"), new Friend("李四"), new Friend("王五") }; } /// <summary> /// 索引器 /// </summary> public Friend this[int index] { get { return _arrFriend[index]; } } public int Count { get { return _arrFriend.Length; } } /// <summary> /// C# 2.0中簡化迭代器的實作 /// </summary> public IEnumerator GetEnumerator() { for (int index = 0; index < _arrFriend.Length; index++) { // 這樣就不需要額外定義一個FriendIterator迭代器來實作IEnumerator // 在C# 2.0中只需要使用下面陳述句就可以實作一個迭代器 yield return _arrFriend[index]; } } } }
使用foreach方式遍歷如下所示:
//在C#2.0中實作迭代器 { Console.WriteLine("在C#2.0中實作迭代器"); var friendCollection = new IteratorImpl.Demo2.Friends(); foreach (Friend item in friendCollection) { Console.WriteLine(item.Name); } }
運行結果如下:

在上面代碼中有一個yield return陳述句,這個陳述句的作用就是告訴編譯器GetEnumerator方法不是一個普通的方法,而是一個實作迭代器的方法,
當編譯器看到yield return陳述句時,編譯器就知道需要實作一個迭代器,所以編譯器生成中間代碼時為我們生成了一個IEnumerator介面的物件,大家可以通過反編譯工具進行查看,
yield return陳述句其實是C#中提供的一個語法糖,簡化我們實作迭代器的代碼,把具體實作復雜迭代器的程序交給編譯器幫我們去完成,
四、迭代器的執行程序
為了讓大家更好的理解迭代器,下面列出迭代器的執行流程:
五、迭代器的延遲計算
從第四部分迭代器的執行程序中可以知道迭代器是延遲計算的,因為迭代的主體在MoveNext()中實作(在MoveNext()方法中訪問了集合中的當前位置的元素),
foreach中每次遍歷執行到in的時候才會呼叫MoveNext()方法,所以迭代器可以延遲計算,下面通過一個示例來演示迭代器的延遲計算:
using System; using System.Collections.Generic; namespace IteratorPattern { /// <summary> /// yield是語法糖,編譯時由編譯器生成Iterrator的代碼 /// </summary> public class YieldDemo { /// <summary> /// 含有迭代器的 /// </summary> public static IEnumerable<int> WithIterator() { for (int i = 0; i < 5; i++) { Console.WriteLine($"在WithIterator方法中的,當前i的值為:{i}"); if (i > 1) { yield return i; } } } /// <summary> /// 不包含迭代器的 /// </summary> public static IEnumerable<int> WithoutIterator() { List<int> list = new List<int>(); for (int i = 0; i < 5; i++) { Console.WriteLine($"在WithoutIterator方法中的,當前i的值為:{i}"); if (i > 1) { list.Add(i); } } return list; } } }
class Program { static void Main(string[] args) { //迭代器的延遲計算 { // 測驗一 YieldDemo.WithIterator(); // 測驗二 YieldDemo.WithoutIterator(); // 測驗三 foreach (var item in YieldDemo.WithIterator()) //按需獲取,要一個拿一個 { Console.WriteLine($"在Main函式的輸出陳述句中,當前i的值為:{item}"); if (item >= 3) { break; } } // 測驗四 foreach (var item in YieldDemo.WithoutIterator()) //先全部獲取,然后一起回傳 { Console.WriteLine($"在Main函式的輸出陳述句中,當前i的值為:{item}"); if (item >= 3) { break; } } } Console.ReadKey(); } }
運行測驗一結果如下:

當運行測驗一的代碼時會發現控制臺中什么都不輸出,這是為什么呢?下面我們通過反編譯工具來看下原因:
PS:此Demo的目標框架最好是Framework版本的,只有這樣才方便通過反編譯工具查看原理,

從反編譯的結果中我們就可以看出測驗一什么都不輸出的原因了,那是因為WithIterator方法中含有yield關鍵字,編譯器遇到yield return陳述句就會幫我們生成一個迭代器類,
從而當我們在測驗一的代碼中呼叫YieldDemo.WithIterator()時,對于編譯器而言其實就是實體化了一個YieldDemo.<WithIterator>d__0的物件而已,所以運行測驗一的代碼時控制臺中什么都不輸出,
運行測驗二結果如下:

運行測驗二結果就如我們期望的那樣輸出,這里就不多解釋了,
運行測驗三結果如下:

運行測驗四結果如下:

對比測驗三和測驗四的結果可以發現迭代器是可以做到延遲計算、按需獲取的,
六、關于迭代器模式的一些小擴展
using System; using System.Collections.Generic; namespace IteratorPattern.Show { public static class ExtendMethod { public static IEnumerable<T> TianYaWhere<T>(this IEnumerable<T> source, Func<T, bool> func) { if (source == null) { throw new Exception("source is null"); } if (func == null) { throw new Exception("func is null"); } foreach (var item in source) { if (func.Invoke(item)) { yield return item; } } } } }
using System; using System.Collections; using System.Collections.Generic; namespace IteratorPattern.Show { public static class LinqExtend { public static IEnumerable<TSource> TianYaWhere<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) { if (source == null) { throw new Exception("source"); } if (predicate == null) { throw new Exception("predicate"); } return new EnumeratorIterator<TSource>(source, predicate); } } public class EnumeratorIterator<TSource> : IEnumerable<TSource> { private IEnumerable<TSource> _source; private Func<TSource, bool> _predicate; public EnumeratorIterator(IEnumerable<TSource> source, Func<TSource, bool> predicate) { this._source = source; this._predicate = predicate; } public IEnumerator<TSource> GetEnumerator() { foreach (var item in this._source) { if (_predicate(item)) { yield return item; } } } IEnumerator IEnumerable.GetEnumerator() { foreach (var item in this._source) { if (_predicate(item)) { yield return item; } } } } }
至此本文就全部介紹完了,如果覺得對您有所啟發請記得點個贊哦!!!
本文部分內容參考博文:https://www.cnblogs.com/zhili/archive/2012/12/02/Interator.html
Demo原始碼:
鏈接:https://pan.baidu.com/s/1FqAvYAZhrKuCLzuJcTZ5KA 提取碼:fx1g
此文由博主精心撰寫轉載請保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/13894175.html
著作權宣告:如有雷同純屬巧合,如有侵權請及時聯系本人修改,謝謝!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/197444.html
標籤:ASP.NET
上一篇:紙殼CMS分布式部署集群解決方案

