国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

詳解C# 迭代器[轉(zhuǎn)]

2019-11-17 02:38:25
字體:
供稿:網(wǎng)友

詳解C# 迭代器[轉(zhuǎn)]

迭代器模式是設(shè)計(jì)模式中行為模式(behavioral pattern)的一個(gè)例子,他是一種簡化對象間通訊的模式,也是一種非常容易理解和使用的模式。簡單來說,迭代器模式使得你能夠獲取到序列中的所有元素 而不用關(guān)心是其類型是array,list,linked list或者是其他什么序列結(jié)構(gòu)。這一點(diǎn)使得能夠非常高效的構(gòu)建數(shù)據(jù)處理通道(data pipeline)--即數(shù)據(jù)能夠進(jìn)入處理通道,進(jìn)行一系列的變換,或者過濾,然后得到結(jié)果。事實(shí)上,這正是LINQ的核心模式。

在.NET中,迭代器模式被IEnumerator和IEnumerable及其對應(yīng)的泛型接口所封裝。如果一個(gè)類實(shí)現(xiàn)了IEnumerable接 口,那么就能夠被迭代;調(diào)用GetEnumerator方法將返回IEnumerator接口的實(shí)現(xiàn),它就是迭代器本身。迭代器類似數(shù)據(jù)庫中的游標(biāo),他是 數(shù)據(jù)序列中的一個(gè)位置記錄。迭代器只能向前移動,同一數(shù)據(jù)序列中可以有多個(gè)迭代器同時(shí)對數(shù)據(jù)進(jìn)行操作。

在C#1中已經(jīng)內(nèi)建了對迭代器的支持,那就是foreach語句。使得能夠進(jìn)行比for循環(huán)語句更直接和簡單的對集合的迭代,編譯器會將 foreach編譯來調(diào)用GetEnumerator和MoveNext方法以及Current屬性,如果對象實(shí)現(xiàn)了IDisposable接口,在迭代 完成之后會釋放迭代器。但是在C#1中,實(shí)現(xiàn)一個(gè)迭代器是相對來說有點(diǎn)繁瑣的操作。C#2使得這一工作變得大為簡單,節(jié)省了實(shí)現(xiàn)迭代器的不少工作。

接下來,我們來看如何實(shí)現(xiàn)一個(gè)迭代器以及C#2對于迭代器實(shí)現(xiàn)的簡化,然后再列舉幾個(gè)迭代器在現(xiàn)實(shí)生活中的例子。

1. C#1:手動實(shí)現(xiàn)迭代器的繁瑣

假設(shè)我們需要實(shí)現(xiàn)一個(gè)基于環(huán)形緩沖的新的集合類型。我們將實(shí)現(xiàn)IEnumerable接口,使得用戶能夠很容易的利用該集合中的所有元素。我們的忽 略其他細(xì)節(jié),將注意力僅僅集中在如何實(shí)現(xiàn)迭代器上。集合將值存儲在數(shù)組中,集合能夠設(shè)置迭代的起始點(diǎn),例如,假設(shè)集合有5個(gè)元素,你能夠?qū)⑵鹗键c(diǎn)設(shè)為2, 那么迭代輸出為2,3,4,0,最后是1. 為了能夠簡單展示,我們提供了一個(gè)設(shè)置值和起始點(diǎn)的構(gòu)造函數(shù)。使得我們能夠以下面這種方式遍歷集合:

object[] values = { "a", "b", "c", "d", "e" };IterationSample collection = new IterationSample(values, 3);foreach (object x in collection){    Console.WriteLine(x);}

由于我們將起始點(diǎn)設(shè)置為3,所以集合輸出的結(jié)果是d,e,a,b及c,現(xiàn)在,我們來看如何實(shí)現(xiàn) IterationSample 類的迭代器:

class IterationSample : IEnumerable{    Object[] values;    Int32 startingPoint;    public IterationSample(Object[] values, Int32 startingPoint)    {        this.values = values;        this.startingPoint = startingPoint;    }    public IEnumerator GetEnumerator()    {        throw new NotImplementedException();    }}

我們還沒有實(shí)現(xiàn)GetEnumerator方法,但是如何寫GetEnumerator部分的邏輯呢,第一就是要將游標(biāo)的當(dāng)前狀態(tài)存在某一個(gè)地方。一方面 是迭代器模式并不是一次返回所有的數(shù)據(jù),而是客戶端一次只請求一個(gè)數(shù)據(jù)。這就意味著我們要記錄客戶當(dāng)前請求到了集合中的那一個(gè)記錄。C#2編譯器對于迭代 器的狀態(tài)保存為我們做了很多工作。 現(xiàn)在來看看,要保存哪些狀態(tài)以及狀態(tài)存在哪個(gè)地方,設(shè)想我們試圖將狀態(tài)保存在IterationSample集合中,使得它實(shí)現(xiàn)IEnumerator和 IEnumerable方法。咋一看,看起來可能,畢竟數(shù)據(jù)在正確的地方,包括起始位置。我們的GetEnumerator方法僅僅返回this。但是這 種方法有一個(gè)很重要的問題,如果GetEnumerator方法調(diào)用多次,那么多個(gè)獨(dú)立的迭代器就會返回。例如,我們可以使用兩個(gè)嵌套的foreach語 句,來獲取所有可能的值對。這兩個(gè)迭代需要彼此獨(dú)立。這意味著我們需要每次調(diào)用GetEnumerator時(shí)返回的兩個(gè)迭代器對象必須保持獨(dú)立。我們?nèi)耘f 可以直接在IterationSample類中通過相應(yīng)函數(shù)實(shí)現(xiàn)。但是我們的類擁有了多個(gè)職責(zé),這位背了單一職責(zé)原則。因此,我們來創(chuàng)建另外一個(gè)類來實(shí)現(xiàn) 迭代器本身。我們使用C#中的內(nèi)部類來實(shí)現(xiàn)這一邏輯。代碼如下:

class IterationSampleEnumerator : IEnumerator{    IterationSample parent;//迭代的對象  #1    Int32 position;//當(dāng)前游標(biāo)的位置 #2    internal IterationSampleEnumerator(IterationSample parent)    {        this.parent = parent;        position = -1;// 數(shù)組元素下標(biāo)從0開始,初始時(shí)默認(rèn)當(dāng)前游標(biāo)設(shè)置為 -1,即在第一個(gè)元素之前, #3    }    public bool MoveNext()    {        if (position != parent.values.Length) //判斷當(dāng)前位置是否為最后一個(gè),如果不是游標(biāo)自增 #4        {            position++;        }        return position < parent.values.Length;    }    public object Current    {        get        {            if (position == -1 || position == parent.values.Length)//第一個(gè)之前和最后一個(gè)自后的訪問非法 #5            {                throw new InvalidOperationException();            }            Int32 index = position + parent.startingPoint;//考慮自定義開始位置的情況  #6            index = index % parent.values.Length;            return parent.values[index];        }    }    public void Reset()    {        position = -1;//將游標(biāo)重置為-1  #7    }}

要實(shí)現(xiàn)一個(gè)簡單的迭代器需要手動寫這么多的代碼:需要記錄迭代的原始集合#1,記錄當(dāng)前游標(biāo)位置#2,返回元素時(shí),根據(jù) 當(dāng)前游標(biāo)和數(shù)組定義的起始位置設(shè)置定迭代器在數(shù)組中的位置#6。初始化時(shí),將當(dāng)前位置設(shè)定在第一個(gè)元素之前#3,當(dāng)?shù)谝淮握{(diào)用迭代器時(shí)首先需要調(diào)用 MoveNext,然后再調(diào)用Current屬性。在游標(biāo)自增時(shí)對當(dāng)前位置進(jìn)行條件判斷#4,使得即使當(dāng)?shù)谝淮握{(diào)用MoveNext時(shí)沒有可返回的元素也 不至于出錯(cuò)#5。重置迭代器時(shí),我們將當(dāng)前游標(biāo)的位置還原到第一個(gè)元素之前#7。 除了結(jié)合當(dāng)前游標(biāo)位置和自定義的起始位置返回正確的值這點(diǎn)容易出錯(cuò)外,上面的代碼非常直觀。現(xiàn)在,只需要在IterationSample類的GetEnumerator方法中返回我們當(dāng)才編寫的迭代類即可:

public IEnumerator GetEnumerator(){    return new IterationSampleEnumerator(this);}

值得注意的是,上面只是一個(gè)相對簡單的例子,沒有太多的狀態(tài)需要跟蹤,不用檢查集合在迭代的過程中是否發(fā)生了變化。為了 實(shí)現(xiàn)一個(gè)簡單的迭代器,在C#1中我們實(shí)現(xiàn)了如此多的代碼。在使用Framework自帶的實(shí)現(xiàn)了IEnumerable接口的集合時(shí)我們使用 foreach很方便,但是當(dāng)我們書寫自己的集合來實(shí)現(xiàn)迭代時(shí)需要編寫這么多的代碼。在C#1中,大概需要40行代碼來實(shí)現(xiàn)一個(gè)簡單的迭代器,現(xiàn)在看看 C#2對這一過程的改進(jìn)。

2. C#2:通過yield語句簡化迭代

2.1 引入迭代塊(iterator)和yield return 語句

C#2使得迭代變得更加簡單--減少了很多代碼量也使得代碼更加的優(yōu)雅。下面的代碼展示了再C#2中實(shí)現(xiàn)GetEnumerator方法的完整代碼:

public IEnumerator GetEnumerator(){    for (int index = 0; index < this.values.Length; index++)    {        yield return values[(index + startingPoint) % values.Length];    }}

簡單幾行代碼就能夠完全實(shí)現(xiàn)IterationSampleIterator類所需要的功能。方法看起來很普通,除了使用了yield return。這條語句告訴編譯器這不是一個(gè)普通的方法,而是一個(gè)需要執(zhí)行的迭代塊(yield block),他返回一個(gè)IEnumerator對象,你能夠使用迭代塊來執(zhí)行迭代方法并返回一個(gè)IEnumerable需要實(shí)現(xiàn)的類型,IEnumerator或者對應(yīng)的泛型。如果實(shí)現(xiàn)的是非泛型版本的接口,迭代塊返的yield typeObject類型,否則返回的是相應(yīng)的泛型類型。例如,如果方法實(shí)現(xiàn)IEnumerable<string>接口,那么yield返回的類型就是String類型。 在迭代塊中除了yield return外,不允許出現(xiàn)普通的return語句。塊中的所有yield return 語句必須返回和塊的最后返回類型兼容的類型。舉個(gè)例子,如果方法定義需要返回IEnumeratble<string>類型的話,不能yield return 1 。 需要強(qiáng)調(diào)的一點(diǎn)是,對于迭代塊,雖然我們寫的方法看起來像是在順序執(zhí)行,實(shí)際上我們是讓編譯器來為我們創(chuàng)建了一個(gè)狀態(tài)機(jī)。這就是在C#1中我們書寫的那部 分代碼---調(diào)用者每次調(diào)用只需要返回一個(gè)值,因此我們需要記住最后一次返回值時(shí),在集合中位置。 當(dāng)編譯器遇到迭代塊是,它創(chuàng)建了一個(gè)實(shí)現(xiàn)了狀態(tài)機(jī)的內(nèi)部類。這個(gè)類記住了我們迭代器的準(zhǔn)確當(dāng)前位置以及本地變量,包括參數(shù)。這個(gè)類有點(diǎn)類似與我們之前手寫 的那段代碼,他將所有需要記錄的狀態(tài)保存為實(shí)例變量。下面來看看,為了實(shí)現(xiàn)一個(gè)迭代器,這個(gè)狀態(tài)機(jī)需要按順序執(zhí)行的操作:

  • 它需要一些初始的狀態(tài);
  • 當(dāng)MoveNext被調(diào)用時(shí),他需要執(zhí)行GetEnumerator方法中的代碼來準(zhǔn)備下一個(gè)待返回的數(shù)據(jù);
  • 當(dāng)調(diào)用Current屬性是,需要返回yielded的值;
  • 需要知道什么時(shí)候迭代結(jié)束是,MoveNext會返回false。
2.2 迭代器的執(zhí)行流程

如下的代碼,展示了迭代器的執(zhí)行流程,代碼輸出(0,1,2,-1)然后終止。

class PRogram {  static readonly String Padding = new String(' ', 30);  static IEnumerable<int32> CreateEnumerable()  {      Console.WriteLine("{0} CreateEnumerable()方法開始", Padding);      for (int i = 0; i &lt; 3; i++)      {          Console.WriteLine("{0}開始 yield {1}", i);          yield return i;          Console.WriteLine("{0}yield 結(jié)束", Padding);      }      Console.WriteLine("{0} Yielding最后一個(gè)值", Padding);      yield return -1;      Console.WriteLine("{0} CreateEnumerable()方法結(jié)束", Padding);  }  static void Main(string[] args)  {      IEnumerable<int32> iterable = CreateEnumerable();      IEnumerator<int32> iterator = iterable.GetEnumerator();      Console.WriteLine("開始迭代");      while (true)      {          Console.WriteLine("調(diào)用MoveNext方法……");          Boolean result = iterator.MoveNext();          Console.WriteLine("MoveNext方法返回的{0}", result);          if (!result)          {              break;          }          Console.WriteLine("獲取當(dāng)前值……");          Console.WriteLine("獲取到的當(dāng)前值為{0}", iterator.Current);      }      Console.ReadKey();  }}

從輸出結(jié)果中可以看出一下幾點(diǎn):

  • 直到第一次調(diào)用MoveNextCreateEnumerable中的方法才被調(diào)用。
  • 在調(diào)用MoveNext的時(shí)候,已經(jīng)做好了所有操作,返回Current屬性并沒有執(zhí)行任何代碼。
  • 代碼在yield return之后就停止執(zhí)行,等待下一次調(diào)用MoveNext方法的時(shí)候繼續(xù)執(zhí)行。
  • 在方法中可以有多個(gè)yield return語句。
  • 在最后一個(gè)yield return執(zhí)行完成后,代碼并沒有終止。調(diào)用MoveNext返回false使得方法結(jié)束。

第一點(diǎn)尤為重要:這意味著,不能在迭代塊中寫任何在方法調(diào)用時(shí)需要立即執(zhí)行的代碼--比如說參數(shù)驗(yàn)證。如果將參數(shù)驗(yàn)證放在迭代塊中,那么他將不能夠很好的起作用,這是經(jīng)常會導(dǎo)致的錯(cuò)誤的地方,而且這種錯(cuò)誤不容易發(fā)現(xiàn)。 下面來看如何停止迭代,以及finally語句塊的特殊執(zhí)行方式。

2.3 迭代器的特殊執(zhí)行流程

在普通的方法中,return語句通常有兩種作用,一是返回調(diào)用者執(zhí)行的結(jié)果。二是終止方法的執(zhí)行,在終止之前執(zhí)行finally語句中的方法。在上面的例子中,我們看到了yield return語句只是短暫的退出了方法,在MoveNext再次調(diào)用的時(shí)候繼續(xù)執(zhí)行。在這里我們沒有寫finally語句塊。如何真正的退出方法,退出方法時(shí)finnally語句塊如何執(zhí)行,下面來看看一個(gè)比較簡單的結(jié)構(gòu):yield break語句塊。 使用 yield break 結(jié)束一個(gè)迭代

static IEnumerable<int32> CountWithTimeLimit(DateTime limit){    try    {        for (int i = 1; i &lt;= 100; i++)        {            if (DateTime.Now >= limit)            {                yield break;            }            yield return i;        }    }    finally    {        Console.WriteLine("停止迭代!"); Console
發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 盱眙县| 汕头市| 南雄市| 武城县| 卢湾区| 呼和浩特市| 柳江县| 木里| 景德镇市| 九龙县| 桐柏县| 民和| 北京市| 虹口区| 涞源县| 汾阳市| 页游| 九龙坡区| 桂东县| 德安县| 平乡县| 安阳市| 怀化市| 綦江县| 深水埗区| 同心县| 宝应县| 临澧县| 隆尧县| 德安县| 武陟县| 犍为县| 砚山县| 彭阳县| 佛坪县| 长葛市| 双柏县| 永兴县| 石嘴山市| 万州区| 阳原县|