Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >C# Foreach循环本质与枚举器

C# Foreach循环本质与枚举器

作者头像
SpringSun
发布于 2020-08-11 06:50:47
发布于 2020-08-11 06:50:47
1.1K00
代码可运行
举报
文章被收录于专栏:技术赋能学术技术赋能学术
运行总次数:0
代码可运行

  对于C#里面的Foreach学过 语言的人都知道怎么用,但是其原理相信很多人和我一样都没有去深究。刚回顾泛型讲到枚举器让我联想到了Foreach的实现,所以进行一番探究,有什么不对或者错误的地方大家多多斧正。

1、创建一个控制台应用程序

2、编写测试代码并分析

在Program类中写一个foreach循环

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 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 "\u5f20\u4e09"
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 "\u674e\u56db"
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 "\u738b\u4e94"
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方法代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public Enumerator GetEnumerator() => new Enumerator((List) this);

即返回一个Enumerator泛型类,然后传入的参数是List泛型自己 this。接下来查看 Enumerator<T>泛型类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[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的另一种写法如下。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-10-22 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C Sharp(十三)
这一篇,我们看看 C# 中的枚举器和迭代器的基本概念。 之前我们说过可以使用 foreach 来遍历数组元素,本篇来讨论为什么数组可以使用 foreach 语句处理,我们可以还使用迭代器来使得自定义类型也可以使用 foreach。
1ess
2021/11/01
6290
C#2.0新增功能05 迭代器
  迭代器方法或 get 访问器可对集合执行自定义迭代。 迭代器方法使用 yield return 语句返回元素,每次返回一个。 到达 yield return 语句时,会记住当前在代码中的位置。 下次调用迭代器函数时,将从该位置重新开始执行。
张传宁IT讲堂
2019/09/17
7470
认真CS☀️枚举器
2、枚举器实现了IEnumerator接口,所以它能做IEnumerator接口定义的所有工作
星河造梦坊官方
2024/08/14
600
[C#3] 3-对象和集合初始化器
1.对象初始化器 首先声明一个类Person: public class Person { public string Name { get; set; } public int Age { get; set; } } 初始化它并调用它: static void Main() { Person person = new Person { Name = "乱舞春秋", Age = 22 }; Console.WriteLine("姓名:{0}",person.Name);
blackheart
2018/01/19
5030
深入理解C# 3.x的新特性(2):Extension Method[下篇]
四、Extension Method的本质 通过上面一节的介绍,我们知道了在C#中如何去定义一个Extension Method:它是定义在一个Static class中的、第一个Parameter标记为this关键字的Static Method。在这一节中,我们来进一步认识Extension Method。 和C# 3.0的其他新特性相似,Extension Method仅仅是C#这种.NET Programming Language的新特性而已。我们知道,C#是一种典型的编译型的语言,我们编写的Sour
蒋金楠
2018/02/07
7050
由for V.S. for each想到的
一直想写一系列如何提高Performance和Scalability的文章,把我的相关经验和所知道的相关的技巧同大家分享。前一阵在园子里有一篇讨论for each 和 for两种循环那个具有更好的performance的blog,议论得沸沸扬扬。我觉得这是一个很好的切入点,我就已此作为引子,开始我的这个系列的文章。这篇文章的重点不是在于比较这两种循环孰优孰劣,我将讨论的重点是如何更好地定义Collection,如何在判断在什么时候该用Array,什么时候用Collection。 一、for each的本质
蒋金楠
2018/02/07
7600
由for V.S. for each想到的
理解C#泛型运作原理
我们都知道泛型在C#的重要性,泛型是OOP语言中三大特征的多态的最重要的体现,几乎泛型撑起了整个.NET框架,在讲泛型之前,我们可以抛出一个问题,我们现在需要一个可扩容的数组类,且满足所有类型,不管是值类型还是引用类型,那么在没有用泛型方法实现,如何实现?
ryzenWzd
2021/03/07
7200
Framework 4.0 新关键字dynamic 之我见(一)
大家好,好久没有写博客了,最近一段时间工作也比较忙,不过我还是会在空余时间去学习一些东西。 微软在这个星期一,开放了VS2010和Framework4.0的Beta1版本的下载,所以我也花了1天的时间进行了下载和安装,因为系统现在是windows7,而且这次版本也是beta版本,所以把它安装在了虚拟机上,虽然只设置了1G的内存,不过跑起来还非常让人满意,没有像其他兄弟说的,很吃内存,很废cpu。 其实先前已经使用过CTP版本了,也一直对新特性在做研究,在第一时间下载了beta版本后,发现其实和CTP版本
脑洞的蜂蜜
2018/02/01
5050
进阶篇:以IL为剑,直指async/await
接上篇:30分钟?不需要,轻松读懂IL,这篇主要从IL入手来理解async/await的工作原理。 先简单介绍下async/await,这是.net 4.5引入的语法糖,配合Task使用可以非常优雅
用户1147588
2018/01/04
1.3K0
进阶篇:以IL为剑,直指async/await
逆向专题 | Writeup分享一
逆向WP分享一 0x01.re4 首先我们先点开运行试玩一下,大意就是让你输入正确的用户名和密码就能拿到flag,接下来进入正题。 丢进IDA中,先shift+F12查看一下字符串(我们所需要的co
安恒网络空间安全讲武堂
2018/02/06
8650
逆向专题 |  Writeup分享一
C# 给多线程传参的三种方式
从《C#高级编程》了解到给线程传递参数有两种方式,一种方式是使用带ParameterizedThreadStart委托参数的Thread构造函数,另一种方式是创建一个自定义类,把线程的方法定义为实例的方法,这样就可以初始化实例的数据,之后启动线程。
vv彭
2021/02/02
3.3K0
[C#6] 7-索引初始化器
0. 目录 C#6 新增特性目录 1. 老版本的代码 1 private static void Main() 2 { 3 var dictionary = new Dictionary<int, string> { 4 { 1, "Value1" }, 5 { 2, "Value2" }, 6 { 3, "Value3" } 7 }; 8 } 早C#3中引入的集合初始化器,可是让我们用上面的语法来在声明一个字典或者集合的时候立即初始化一些项进
blackheart
2018/01/19
6470
内存优化:Boxing
如今,许多开发人员都熟悉性能分析的工作流程:在分析器下运行应用程序,测量方法的执行时间,识别占用时间较多的方法,并致力于优化它们。然而,这种情况并没有涵盖到一个重要的性能指标:应用程序多次GC所分配的时间。当然,你可以评估GC所需的总时间,但是它从哪里来,如何减少呢? “普通”性能分析不会给你任何线索。
用户10786849
2024/06/07
1410
内存优化:Boxing
C#基础知识系列九(对IEnumerable和IEnumerator接口的糊涂认识)
   IEnumerable、IEnumerator到现在为止对这两个接口还是不太理解,不理解但是自己总是想着试着要搞明白,毕竟自己用的少,所以在此先记录一下。以备自己日后可以来翻查,同时也希望园子里的大牛们,来帮我看看理解的怎么样。
aehyok
2018/08/31
6080
C#基础知识系列九(对IEnumerable和IEnumerator接口的糊涂认识)
CA1010:集合应实现泛型接口
类型实现 System.Collections.IEnumerable 接口,但不能实现 System.Collections.Generic.IEnumerable<T> 接口和包含程序集的目标 .NET。 此规则会忽略能够实现 System.Collections.IDictionary 的类型。
用户4268038
2022/01/10
6360
C# 循环的判断会进来几次
最近有小伙伴告诉我,在循环的判断条件只会计算一次,本金鱼不相信,于是就做了测试,本文记录我做的测试。
林德熙
2018/09/19
6960
C# 循环的判断会进来几次
【深入浅出.Net IL】1.一个For循环引发的IL
1.准备工作 (1)新建一个project using System; namespace newArr { class Program { static void Main(string[] args) { char[] arrchars = new char[5]; for (int i = 0; i < 5; i++) { arrchars[i] =
悟空聊架构
2018/05/18
6310
C#:数据结构queue队列源码:循环使用数组头标,尾标,防止不停扩容数组
https://referencesource.microsoft.com/#mscorlib/system/collections/queue.cs
立羽
2023/08/24
1880
C# 里面很少人知道但很好用的 Tuple 转换
小伙伴们都知道有 Tuple 但是很少有小伙伴只有原来从一个类转换为一个 Tuple 的方式如此简洁,在 C# 最新版本里面提供了一组语法糖,可以便捷给任意的类扩展转换为元组的能力
林德熙
2020/04/08
5370
迭代器模式 与 C# IEnumerator/IEnumerable
在软件构建过程中,集合对象内部结构常常变化各异。但对于这些集合对象,我们希望在不暴露其内部结构的同时,可以让外部客户代码透明地访问其中包含的元素;同时这种“透明遍历”也为“同一种算法在多种集合对象上进行操作”提供了可能。使用面向对象技术将这种遍历机制抽象为“迭代器对象”为“应对变化中的集合对象”提供了一种优雅的方式。
jgrass
2024/12/25
1160
相关推荐
C Sharp(十三)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验