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是否是一樣的。
1 IL_002c: br.s IL_003d //for開始的地方 2 IL_002e: nop 3 IL_002f: ldloc.0 4 IL_0030: ldloc.1 5 IL_0031: ldelem.ref 6 IL_0032: call void [mscorlib]System.Console::WriteLine(string) 7 IL_0037: nop 8 IL_0038: nop 9 IL_0039: ldloc.1 // 10 IL_003a: ldc.i4.1 // 11 IL_003b: add //索引加1,這裡的索引是已經保存在堆棧中的索引 12 IL_003c: stloc.1 13 IL_003d: ldloc.1 14 IL_003e: ldloc.0 15 IL_003f: ldlen 16 IL_0040: conv.i4 17 IL_0041: clt 18 IL_0043: stloc.s CS$4$0001 19 IL_0045: ldloc.s CS$4$0001 20 IL_0047: brtrue.s IL_002e //跳轉到第2行 21 IL_0049: nop 22 IL_004a: ldloc.0 23 IL_004b: stloc.s CS$6$0002 24 IL_004d: ldc.i4.0 25 IL_004e: stloc.s CS$7$0003 26 IL_0050: br.s IL_0067 //foreach開始的地方 27 IL_0052: ldloc.s CS$6$0002 28 IL_0054: ldloc.s CS$7$0003 29 IL_0056: ldelem.ref 30 IL_0057: stloc.2 31 IL_0058: nop 32 IL_0059: ldloc.2 33 IL_005a: call void [mscorlib]System.Console::WriteLine(string) 34 IL_005f: nop 35 IL_0060: nop 36 IL_0061: ldloc.s CS$7$0003 // 37 IL_0063: ldc.i4.1 // 38 IL_0064: add //當前索引處加1 39 IL_0065: stloc.s CS$7$0003 40 IL_0067: ldloc.s CS$7$0003 41 IL_0069: ldloc.s CS$6$0002 42 IL_006b: ldlen 43 IL_006c: conv.i4 44 IL_006d: clt 45 IL_006f: stloc.s CS$4$0001 46 IL_0071: ldloc.s CS$4$0001 47 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接口成員。
1 public bool MoveNext()
2 {
3 int num;
4 if (this.version != this.list._version)
5 {
6 throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_EnumFailedVersion"));
7 }
8 if (this.isArrayList)
9 {
10 if (this.index < (this.list._size - 1))
11 {
12 num = this.index + 1;
13 this.index = num;
14 this.currentElement = this.list._items[num]; //其實還是取得內部的數組變量的成員
15 return true;
16 }
17 this.currentElement = dummyObject;
18 this.index = this.list._size;
19 return false;
20 }
21 if (this.index < (this.list.Count - 1))
22 {
23 num = this.index + 1;
24 this.index = num;
25 this.currentElement = this.list[num]; //數組變量的成員
26 return true;
27 }
28 this.index = this.list.Count;
29 this.currentElement = dummyObject;
30 return false;
31 }
在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;
}
}
}