IL角度理解for 與foreach的區別——迭代器模式

IL角度理解for 與foreach的區別——迭代器模式

1 最經常使用的設計模式

1.1 背景

若是問你最經常使用的設計模式是哪一種?你可能會說單例模式,工廠模式。但根據我在項目裏的經驗,一個完整的應用,應該是迭代器模式。html

1.2 摘要

  • 本文不講怎麼去實現迭代器模式,但介紹迭代器模式究竟是什麼?爲何迭代器循環迭代時,沒法刪除元素,沒法修改元素;
  • 本文講的迭代器,媒介主要是C#語言下的foreach,微軟爸爸已經在C#的foreach中幫咱們實現了迭代器代碼,關於迭代器,咱們只須要知道他是什麼,他的特性是什麼,爲何要用他?
  • 迭代器有什麼優勢,有什麼缺點?
  • 在本文中迭代器模式約等於foreach

2 遍歷元素

在本項中將會帶你們一塊兒進入迭代器模式下的IL代碼,看看迭代器內部是怎麼去進行循環迭代的;
以下代碼實現了for循環打印集合中的全部元素,以及foreach迭代打印集合中的全部元素。算法

static void Main(string[] args)
        {
            List<int> intList = new List<int>();
            intList.Add(1);
            intList.Add(2);
            for(var i=0;i<intList.Count;i++)
            {
                Console.WriteLine("元素for遍歷打印:" + intList[i]);
            }

            foreach (var item in intList)
            {
                Console.WriteLine("元素foreach遍歷打印:" + item);
            }
        }

代碼輸出:
設計模式

能夠看到兩種打印方式效果一致;for循環任一一種語言都有,你們再熟悉不過,這裏再也不贅述,咱們來看下foreach迭代器模式的IL代碼(若是當前網頁過小,看不清,請將圖片右鍵另存打開,或者將圖片拖到瀏覽器新窗口打開);
數組

大概思路以下:List 下有個迭代器Enumerator,能夠經過List下的GetEnumerator方法得到此迭代器集合;而後經過迭代器的get_Current方法來獲取當前第一個元素。迭代器的MoveNext()方法判斷下一元素是否存在,若是存在取出循環;不存在結束迭代循環模式;最終,將迭代過程當中產生的系統非託管資源釋放;瀏覽器

迭代器代碼以下:設計

var enumerator = intList.GetEnumerator();
            while (enumerator.MoveNext())
            {
                Console.WriteLine("迭代器模式遍歷打印:" + enumerator.Current);
            }
            Console.ReadLine();

3 刪除元素

3.1 for刪除例子

List<int> intList = new List<int>();
            intList.Add(1);
            intList.Add(2);

            for(var i=0;i<intList.Count;i++)
            {
                if (intList[i] == 1)
                { intList.Remove(1); }
            }
            Console.WriteLine("集合中元素數量:" + intList.Count);

Console輸出
集合中元素數量:1
這說明for刪除成功code

3.2 foreach刪除例子

foreach (var item in intList)
             {
                if (item==1)
                { intList.Remove(1); }
             }
            Console.WriteLine("集合中元素數量:" + intList.Count);

從如上拋出的異常得得知 var enumerator = intList.GetEnumerator();迭代器在循環迭代中是不容許對該迭代器進行刪除操做的。若是須要刪除操做,能夠再這個集合基礎上作linq操做;由於迭代器在循環迭代中是一個值對象的存在,不容許變動迭代器對象的內容;htm

思考對象

以下代碼也是運用了迭代器模式,爲何能刪除集合元素了?讀者自行思考哈。這也是迭代器方式下犧牲內存來刪除/修改元素的一種方法。blog

foreach (var item in dictionary.ToList()) {
  Debug.Log(item.Key + "," + item.Value);
  if (item.Key.Equals("s11")) {
    dictionary.Remove(item.Key);
  }
}

4 修改元素

4.1 for修改

List<int> intList = new List<int>();
            intList.Add(1);
            intList.Add(2);

           for(var i=0;i<intList.Count;i++)
           {
               if (intList[i] == 1) 
               { intList[i]=3; }
                Console.WriteLine("集合中元素遍歷:" + intList[i]);
           }

輸出:

集合中元素遍歷:3  
集合中元素遍歷:2

從如上輸出看出,for修改爲功

4.2 foreach修改

直接編譯就通不過

5 修改元素屬性

以下程序,會去修改迭代器元素的屬性,而且在for跟foreach下都能運行成功。

class Program
    {
        static void Main(string[] args)
        {
            List<protocol> protocols = new List<protocol>();
            var p1 = new protocol();
            p1.bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
            var p2 = new protocol();
            p2.bytes = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
            protocols.Add(p1);
            protocols.Add(p2);
           foreach (var item in protocols)
             {
               item.bytes = p2.bytes;
               item.intList.Add(1);
            }

             for (var i=0;i<protocols.Count;i++)
             {
                protocols[i].bytes = p1.bytes;
                protocols[i].intList.Add(1);
                protocols[i] = p1;
            }
            Console.WriteLine("程序運行成功");
            Console.ReadLine();

        }
    }
    public class protocol
    {
        public byte[] bytes= { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
        public List<int> intList=new List<int>();
    }

6 什麼場景下用foreach

既然,for效率更高,foreach效率不及for。for能作的事foreach不能作(修改刪除元素)。是否是沒有foreach的用武之地了呢,大錯特錯了,偏偏相反,每一個項目裏用的最多的反而是foreach迭代器模式,我統計了下超過工廠模式跟單例模式,不信你本身去數foreach的數量。可是在遍歷散列集合時,如字典,for就會失效了,由於下標不是有序數值了,而是看上去無序的內部散列算法。這時foreach遍歷字典就頗有用,他定義了一個枚舉器(迭代器),而這個枚舉器統一了,getCurrent(), moveNext()等用於迭代的方法;

foreach (var item in dictionary.ToList()) {
  Debug.Log(item.Key + "," + item.Value);
  if (item.Key.Equals("s11")) {
    dictionary.Remove(item.Key);
  }
}

像上面的用for,for循環的i與字典的key(自定義,通常是string類型)是沒辦法創建映射關係的;因此沒法用for循環

7 小結

  1. for與foreach均可以遍歷數組/集合,不過for則在較複雜的循環中效率更高;
  2. foreach不能夠刪除/修改集合元素,而for能夠;
  3. foreach和for均可以修改元素裏面的屬性;
  4. foreach適合遍歷用了哈希算法的散列,集合,字典;

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。 本文連接:http://www.javashuo.com/article/p-odyaqksf-nv.html

相關文章
相關標籤/搜索