C# IEnumerable、IEnumerator和foreach的联系与解析
2016-06-22 11:37
459 查看
1、关于foreach和for
foreach和for都是循环的关键字,使用这两个关键字可以对集合对象进行遍历,获取里面每一个对象的信息进行操作。
上面结果的输出都是一样的,我们来看看IL是否是一样的。
从IL可以看出,for中循环的索引是for自身的索引(即i),foreach在循环过程中会在指定位置存储一个值,这个值就是循环用的索引。所以,其实foreach内部还是存储了一个索引值用于循环,只是我们在用的过程中没有察觉到存在这个变量而已。
我们再来看看下面这个例子:
编译出错 : “str”是一个“foreach 迭代变量”,无法为它赋值
同样,编译器给出了相同的错误。
那么如果在foreach中移除当前项呢?
运行出现了异常
可以看出移除IEnumerable类型的变量也会出错,所以在foreach中是不能改变进行迭代的集合对象值的。
2、foreach和IEnumerable的联系
像List,Array等集合类型,可以使用for和foreach来对其进行循环迭代,获得每一个集合内的对象用于操作。之所以可以使用foreach,是因为List,Array等类型实现了IEnumerable或者IEnumerable<T>接口。
IEnumerable接口内部只有一个方法,GetEnumerator()方法,返回值是一个IEnumerator类型的对象。
可以看出,在IEnumerator接口中有三个成员,用于移动位置的MoveNext函数,表示当前对象的Current属性,重置函数Reset。
我们以ArrayList类型为例,来看看这个接口是怎么实现的。
首先内部有一个数组变量用于存储遍历的集合对象。
在内部私有的类ArrayListEnumeratorSimple中实现了IEnumerator接口成员。
在MoveNext中进行迭代循环的时候迭代的是内部的_items数组,即每次取的值都是_items的成员,而_items数组是ArrayList的索引数组。每次迭代后都会保存当前索引值用于下次使用。
所以不难看出,IEnumerator接口内部实现的方式归根结底还是和for实现的方式一样的。
之所以修改枚举值过后继续访问会抛出InvalidOperationException异常是因为以下代码:
在Reset和MoveNext中都有这个判断,如果枚举值被修改了,他所对应的版本号将会发生改变(在Remove函数中将会执行this._version++,使得版本号发生了改变,其他改变枚举值状态的函数类似)。
3、自定义实现迭代器
具体实现代码:
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; } } }
相关文章推荐
- C#使用winform简单导出Excel的方法
- C# button 去边框
- C#控制Excel Sheet使其自适应页宽与列宽的方法
- c#取得应用程序根目录
- c# sleep 例子
- C# System.IO命名空间常用的类
- C#金额小写变大写
- c# mouseenter mousemove区别?
- c# 添加了按钮双击事件后,再删除掉代码会提示错误
- C#中高效的数据插入方法
- C#动态调用webservice
- c# winform 点击按钮切换tabcontrol标签
- C# 调用外部程序Process类
- C#壓縮文件幫助類 使用ICSharpCode.SharpZipLib.dll
- C#委托,事件理解入门
- [C#]winform 取消右上角关闭按钮方法
- [C#]C# 最小化 托盘
- C#正则表达式编程(四):正则表达式
- C#正则表达式编程(二):Regex类用法
- C#正则表达式编程(一):C#中有关正则的类