1. 程式人生 > >C# 傳統遍歷與迭代器

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的基礎。

參考連結: