C# Foreach循环本质与枚举器
对于C#里面的Foreach学过 语言的人都知道怎么用,但是其原理相信很多人和我一样都没有去深究。刚回顾泛型讲到枚举器让我联想到了Foreach的实现,所以进行一番探究,有什么不对或者错误的地方大家多多斧正。
1、创建一个控制台应用程序
2、编写测试代码并分析
在Program类中写一个foreach循环
class Program
{
static void Main(string[] args)
{
List peopleList = new List() { "张三", "李四", "王五" };
foreach (string people in peopleList)
{
Console.WriteLine(people);
}
Console.ReadKey();
}
}
生成项目将项目编译后在debug目录下用Reflection反编译ForeachTest.exe程序集后查看Program类的IL代码,IL代码如下:
1 .class private auto ansi beforefieldinit Program
2 extends [mscorlib]System.Object
3 {
4 .method public hidebysig specialname rtspecialname instance void .ctor() cil managed
5 {
6 .maxstack 8
7 L_0000: ldarg.0
8 L_0001: call instance void [mscorlib]System.Object::.ctor()
9 L_0006: ret
10 }
11
12 .method private hidebysig static void Main(string[] args) cil managed
13 {
14 .entrypoint
15 .maxstack 2
16 .locals init (
17 [0] class [mscorlib]System.Collections.Generic.List`1<string> list,
18 [1] string str,
19 [2] class [mscorlib]System.Collections.Generic.List`1<string> list2,
20 [3] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator`0<string> enumerator,
21 [4] bool flag)
22 L_0000: nop
23 L_0001: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
24 L_0006: stloc.2
25 L_0007: ldloc.2
26 L_0008: ldstr "u5f20u4e09"
27 L_000d: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
28 L_0012: nop
29 L_0013: ldloc.2
30 L_0014: ldstr "u674eu56db"
31 L_0019: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
32 L_001e: nop
33 L_001f: ldloc.2
34 L_0020: ldstr "u738bu4e94"
35 L_0025: callvirt instance void [mscorlib]System.Collections.Generic.List`1<string>::Add(!0)
36 L_002a: nop
37 L_002b: ldloc.2
38 L_002c: stloc.0
39 L_002d: nop
40 L_002e: ldloc.0
41 L_002f: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator`0<!0> [mscorlib]System.Collections.Generic.List`1<string>::GetEnumerator()
42 L_0034: stloc.3
43 L_0035: br.s L_0048
44 L_0037: ldloca.s enumerator
45 L_0039: call instance !0 [mscorlib]System.Collections.Generic.List`1/Enumerator`0<string>::get_Current()
46 L_003e: stloc.1
47 L_003f: nop
48 L_0040: ldloc.1
49 L_0041: call void [mscorlib]System.Console::WriteLine(string)
50 L_0046: nop
51 L_0047: nop
52 L_0048: ldloca.s enumerator
53 L_004a: call instance bool [mscorlib]System.Collections.Generic.List`1/Enumerator`0<string>::MoveNext()
54 L_004f: stloc.s flag
55 L_0051: ldloc.s flag
56 L_0053: brtrue.s L_0037
57 L_0055: leave.s L_0066
58 L_0057: ldloca.s enumerator
59 L_0059: constrained. [mscorlib]System.Collections.Generic.List`1/Enumerator`0<string>
60 L_005f: callvirt instance void [mscorlib]System.IDisposable::Dispose()
61 L_0064: nop
62 L_0065: endfinally
63 L_0066: nop
64 L_0067: call valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
65 L_006c: pop
66 L_006d: ret
67 .try L_0035 to L_0057 finally handler L_0057 to L_0066
68 }
69 }
在反编译的IL代码中我们看到除了构建List和其他输出,然后多了三个方法:GetEnumerator(),get_Current() ,MoveNext() ,于是通过反编译reflector查看List泛型类,在List里面找到GetEnumerator方法是继承自接口IEnumerable 的方法,List实现的GetEnumerator方法代码
public Enumerator GetEnumerator() => new Enumerator((List) this);
即返回一个Enumerator泛型类,然后传入的参数是List泛型自己 this。接下来查看 Enumerator<T>泛型类
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : IEnumerator<T>, IDisposable, IEnumerator
{
private List<T> list;
private int index;
private int version;
private T current;
internal Enumerator(List<T> list)
{
this.list = list;
this.index = 0;
this.version = list._version;
this.current = default(T);
}
public void Dispose()
{
}
public bool MoveNext()
{
List<T> list = this.list;
if ((this.version == list._version) && (this.index < list._size))
{
this.current = list._items[this.index];
this.index++;
return true;
}
return this.MoveNextRare();
}
private bool MoveNextRare()
{
if (this.version != this.list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
this.index = this.list._size + 1;
this.current = default(T);
return false;
}
public T Current =>
this.current;
object IEnumerator.Current
{
get
{
if ((this.index == 0) || (this.index == (this.list._size + 1)))
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
}
return this.Current;
}
}
void IEnumerator.Reset()
{
if (this.version != this.list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
this.index = 0;
this.current = default(T);
}
}
我们看到这个Enumerator<T>泛型类实现了接口IEnumerator的方法,也就是我们测试的ForeachTest程序集反编译后IL代码中出现的get_Current() ,MoveNext() 方法。所以foreach实际上是编译器编译后先调用GetEnumerator方法返回Enumerator的实例,这个实例即是一个枚举器实例。通过MoveNext方法移动下标来查找下一个list元素,get_Current方法获取当前查找到的元素,Reset方法是重置list。
3、总结
因此要使用Foreach遍历的对象是继承了IEnumerable接口然后实现GetEnumerator方法。返回的实体对象需要继承IEnumerator接口并实现相应的方法遍历对象。因此Foreach的另一种写法如下。
- 苹果首个自动驾驶专利到底有什么来头?
- 围棋遇上互联网:科技打开优秀传统文化未来之门
- 神经网络开始放飞自我!都是因为架构搜索新算法
- 浏览器缓存问题的解决
- nginx下目录浏览及其验证功能、版本隐藏等配置记录
- Sqlite的多表连接更新
- Enterprise Library 4.1学习笔记6----加密应用程序块
- 浅谈数据库主键策略
- nginx应用总结(1)--基础认识和应用配置
- nginx反向代理tomcat访问时浏览器加载失败,出现 ERR_CONTENT_LENGTH_MISMATCH 问题
- Enterprise Library 4.1学习笔记5----实体验证程序块
- Python防止sql注入
- 电工学PLC编程的入门建议
- 人工智能、区块链、图灵测试……这30个大数据热词你真的都懂吗?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Redis过期策略以及淘汰机制
- 几行代码就可以轻松给你的程序加上进度条
- git禁止在master分支push和commit
- 记录一次mybatis缓存和事务传播行为导致ut挂的排查过程
- appium教程_3.启动appium-server
- appium教程_4.adb常用命令
- Python中的高阶概念属性:五个你应该搞明白的知识点
- 一次奇怪的http状态码改变
- Salesforce LWC学习(二十七) File Upload
- 让我们来谈谈python中的prettyprint和pprint
- vue 开发规范
- Markdown 编写规范
- JavaScript编码规范
- HTML编码规范
- postgres数据库不能用ip地址访问的问题