程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#基礎知識 >> C# IEnumerable、IEnumerator和foreach的聯系與解析

C# IEnumerable、IEnumerator和foreach的聯系與解析

編輯:C#基礎知識

1、關於foreach和for

foreach和for都是循環的關鍵字,使用這兩個關鍵字可以對集合對象進行遍歷,獲取裡面每一個對象的信息進行操作。

  static void Main(string[] args)
        {
            string[] strList = new string[]
            {
                "1","2","3","4"
            };

            for (int i = 0; i < strList.Length; i++)
            {
                Console.WriteLine(strList[i]);
            }

            foreach (string str in strList)
            {
                Console.WriteLine(str);
            }

            Console.ReadKey();
        }

上面結果的輸出都是一樣的,我們來看看IL是否是一樣的。

 IL_002c:  br.s       IL_003d   //for開始的地方
   IL_002e:  nop
   IL_002f:  ldloc.0
   IL_0030:  ldloc.1
   IL_0031:  ldelem.ref
   IL_0032:  call       void [mscorlib]System.Console::WriteLine(string)
   IL_0037:  nop
   IL_0038:  nop
   IL_0039:  ldloc.1  //
   IL_003a:  ldc.i4.1 //
   IL_003b:  add      //索引加1,這裡的索引是已經保存在堆棧中的索引
   IL_003c:  stloc.1
   IL_003d:  ldloc.1
   IL_003e:  ldloc.0
   IL_003f:  ldlen
   IL_0040:  conv.i4
   IL_0041:  clt
   IL_0043:  stloc.s    CS$4$0001
   IL_0045:  ldloc.s    CS$4$0001
   IL_0047:  brtrue.s   IL_002e   //跳轉到第2行
   IL_0049:  nop
   IL_004a:  ldloc.0
   IL_004b:  stloc.s    CS$6$0002
   IL_004d:  ldc.i4.0
   IL_004e:  stloc.s    CS$7$0003
   IL_0050:  br.s       IL_0067   //foreach開始的地方
   IL_0052:  ldloc.s    CS$6$0002
   IL_0054:  ldloc.s    CS$7$0003
   IL_0056:  ldelem.ref
   IL_0057:  stloc.2
   IL_0058:  nop
   IL_0059:  ldloc.2
   IL_005a:  call       void [mscorlib]System.Console::WriteLine(string)
   IL_005f:  nop
   IL_0060:  nop
   IL_0061:  ldloc.s    CS$7$0003  //
   IL_0063:  ldc.i4.1              //
   IL_0064:  add                   //當前索引處加1
   IL_0065:  stloc.s    CS$7$0003
   IL_0067:  ldloc.s    CS$7$0003
   IL_0069:  ldloc.s    CS$6$0002
   IL_006b:  ldlen
   IL_006c:  conv.i4
   IL_006d:  clt
   IL_006f:  stloc.s    CS$4$0001
   IL_0071:  ldloc.s    CS$4$0001
   IL_0073:  brtrue.s   IL_0052     //跳轉到27行

從IL可以看出,for中循環的索引是for自身的索引(即i),foreach在循環過程中會在指定位置存儲一個值,這個值就是循環用的索引。所以,其實foreach內部還是存儲了一個索引值用於循環,只是我們在用的過程中沒有察覺到存在這個變量而已。

我們再來看看下面這個例子:

 static void RunFor()
        {
            string[] strList = new string[]
            {
                "1","2","3","4"
            };

            for (int i = 0; i < strList.Length; i++)
            {
                strList[i] = "1";
            }
        }

        static void RunForeach()
        {
            string[] strList = new string[]
            {
                "1","2","3","4"
            };

            foreach (string str in strList)
            {
                str = "1";
            }
        }

編譯出錯 : “str”是一個“foreach 迭代變量”,無法為它賦值

 static void RunFor()
        {
            List<string> strList = new List<string>()
            {
                "1","2","3","4"
            };

            for (int i = 0; i < strList.Count; i++)
            {
                strList[i] = "1";
            }
        }

        static void RunForeach()
        {
            List<string> strList = new List<string>()
            {
                "1","2","3","4"
            };

            foreach (string str in strList)
            {
                str = "1";
            }
        }

同樣,編譯器給出了相同的錯誤。

那麼如果在foreach中移除當前項呢?

class Program
    {
        static void Main(string[] args)
        {
            List<string> strs = new List<string>() { "1", "2", "3", "4" };
            foreach (string str in strs)
            {
                strs.Remove(str);
            }
            Console.ReadKey();
        }
    }

運行出現了異常

可以看出移除IEnumerable類型的變量也會出錯,所以在foreach中是不能改變進行迭代的集合對象值的。

2、foreach和IEnumerable的聯系

像List,Array等集合類型,可以使用for和foreach來對其進行循環迭代,獲得每一個集合內的對象用於操作。之所以可以使用foreach,是因為List,Array等類型實現了IEnumerable或者IEnumerable<T>接口。

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

IEnumerable接口內部只有一個方法,GetEnumerator()方法,返回值是一個IEnumerator類型的對象。

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

可以看出,在IEnumerator接口中有三個成員,用於移動位置的MoveNext函數,表示當前對象的Current屬性,重置函數Reset。

 我們以ArrayList類型為例,來看看這個接口是怎麼實現的。

首先內部有一個數組變量用於存儲遍歷的集合對象。

object[] _items;

在內部私有的類ArrayListEnumeratorSimple中實現了IEnumerator接口成員。

  public bool MoveNext()
     {
         int num;
         if (this.version != this.list._version)
         {
             throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion"));
         }
         if (this.isArrayList)
         {
             if (this.index < (this.list._size - 1))
             {
                 num = this.index + 1;
                 this.index = num;
                 this.currentElement = this.list._items[num]; //其實還是取得內部的數組變量的成員
                 return true;
             }
             this.currentElement = dummyObject;
             this.index = this.list._size;
             return false;
         }
         if (this.index < (this.list.Count - 1))
         {
             num = this.index + 1;
             this.index = num;
             this.currentElement = this.list[num]; //數組變量的成員
             return true;
         }
         this.index = this.list.Count;
         this.currentElement = dummyObject;
         return false;
     }

 在MoveNext中進行迭代循環的時候迭代的是內部的_items數組,即每次取的值都是_items的成員,而_items數組是ArrayList的索引數組。每次迭代後都會保存當前索引值用於下次使用。

 所以不難看出,IEnumerator接口內部實現的方式歸根結底還是和for實現的方式一樣的。

之所以修改枚舉值過後繼續訪問會拋出InvalidOperationException異常是因為以下代碼:

 if (this.version != this.list._version)
  {
     throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion"));
  }

在Reset和MoveNext中都有這個判斷,如果枚舉值被修改了,他所對應的版本號將會發生改變(在Remove函數中將會執行this._version++,使得版本號發生了改變,其他改變枚舉值狀態的函數類似)。

3、自定義實現迭代器

 具體實現代碼:

 class Program
    {
        static void Main(string[] args)
        {
            TestIEnumerable test = new TestIEnumerable();
            foreach (string str in test)
            {
                Console.WriteLine(str);
            }
            Console.ReadKey();
        }
    }

    class TestIEnumerable : IEnumerable
    {
        private string[] _item;

        public TestIEnumerable()
        {
            _item = new string[]
             {
                 "1","2","3","4"
             };
        }

        public string this[int index]
        {
            get { return _item[index]; }
        }

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

        class EnumeratorActualize : IEnumerator
        {
            private int index;
            private TestIEnumerable _testEnumerable;
            private object currentObj;
            public EnumeratorActualize(TestIEnumerable testEnumerable)
            {
                _testEnumerable = testEnumerable;
                currentObj = new object();
                index = -1;
            }


            public object Current
            {
                get
                {
                    return currentObj;
                }
            }

            public bool MoveNext()
            {
                if (index < _testEnumerable._item.Length - 1)
                {
                    index++;
                    currentObj = _testEnumerable._item[index];
                    return true;
                }
                index = _testEnumerable._item.Length;
                return false;
            }

            public void Reset()
            {
                index = -1;
            }
        }
    }
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved