从IL认识关键字(二)
关键字
上一篇研究了foreach关键字,foreach关键字需要实现IEnumerable接口,实现GetEnumerator()方法。实现Enumerator()方法,很多时候用到yield关键字,今天我们研究一下yield关键字。
MSDN解释
- yield 关键字向编译器指示它所在的方法是迭代器块。
- 编译器生成一个类来实现迭代器块中表示的行为。
- 在迭代器块中, yield 关键字与 return 关键字结合使用,向枚举器对象提供值。
- yield 关键字也可与 break 结合使用,表示迭代结束。
其实MSDN已经解释清楚,今天我们主要研究第二点,C#会根据yield关键字生成一个类,看看这个类是怎样的。
C# IL Code
先从简单例子看,只有一个值的迭代器
public static IEnumerable Test() { yield return 10; }
对应的IL
.maxstack 2 .locals init ( [0] class Yield.Program/<Test>d__0 d__, [1] class [mscorlib]System.Collections.IEnumerable enumerable) L_0000: ldc.i4.s -2 L_0002: newobj instance void Yield.Program/<Test>d__0::.ctor(int32) L_0007: stloc.0 L_0008: ldloc.0 L_0009: stloc.1 L_000a: br.s L_000c L_000c: ldloc.1 L_000d: ret
<Test>d_0这个就是编译器生成的一个类,这段代码翻译成真正的运行代码如下
public static System.Collections.IEnumerable YieldOnly() { return new d__0(-2); }
public <Test>d__0(int <>1__state) { this.<>1__state = <>1__state; this.<>l__initialThreadId = Thread.CurrentThread.ManagedThreadId; }
这个就是<Test>d_0的构造函数。那究竟d_0这个类是怎样的,由于篇幅有限,这里不全贴出来。有兴趣同学自己可以反编译一下,看看就清楚。它生成时一个密封类,基础IEnumerable,IEnumerator两个接口,说白了就是一个迭代器。
[CompilerGenerated] private sealed class <Test>d__0 : IEnumerable<object>, IEnumerable, IEnumerator<object>, IEnumerator, IDisposable { .... }
下面主要贴出两个方法解释。
private bool MoveNext() { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<>2__current = 10; this.<>1__state = 1; return true; case 1: this.<>1__state = -1; break; } return false; }
看到这里有人奇怪 _state = -2, 这里返回false,就不能遍历。上一篇我们说了foreach的原理,它是先调用GetEnumerator()获取迭代器对象,再遍历。
IEnumerator<object> IEnumerable<object>.GetEnumerator() { if ((Thread.CurrentThread.ManagedThreadId == this.<>l__initialThreadId) && (this.<>1__state == -2)) { this.<>1__state = 0; return this; } return new Program.<Test>d__0(0); }
初始化放在这里,这里不知道为什么会判断一个是不是当前的线程,难道它还会跨线程调用。
循环的yield
public System.Collections.IEnumerable YieldCycle() { for (int i = 0; i < 10; i++) { yield return i; } }
对应MoveNext()
private bool MoveNext() { switch (this.<>1__state) { case 0: this.<>1__state = -1; this.<i>5__7 = 0; while (this.<i>5__7 < 10) {
//1.迭代 this.<>2__current = this.<i>5__7; this.<>1__state = 1; return true;
//2.叠加 Label_004B: this.<>1__state = -1; this.<i>5__7++; } break; case 1: goto Label_004B; } return false; }
循环语句基本分为两个模块,一个迭代的部分,一个是叠加的部分。对于有判断条件的yield return的,会将叠加部分拆分。
下一篇关键字
以上只是本人的理解与实践,如有错误不足之处希望理解包容,下一篇讨论using关键字
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?