下面的 C# 是一个非常简单的循环,但我认为它是两个循环。我的一个同事说他认为这是一个单循环。你能告诉我它是一个循环还是两个循环?你也能告诉我如何阅读 IL 并向我的同事证明它是两个循环吗?
var ints = new List<int> {1, 2, 3, 4};
foreach (var i in ints.Where(x => x != 2))
{
Console.WriteLine(i);
}
如果事实证明这实际上是一个很酷的循环。我仍然想知道如何读取 IL 并看到它只是一个循环。
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 137 (0x89)
.maxstack 3
.locals init ([0] class [mscorlib]System.Collections.Generic.List`1<int32> ints,
[1] int32 i,
[2] class [mscorlib]System.Collections.Generic.List`1<int32> '<>g__initLocal0',
[3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000,
[4] bool CS$4$0001)
IL_0000: nop
IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
IL_0006: stloc.2
IL_0007: ldloc.2
IL_0008: ldc.i4.1
IL_0009: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_000e: nop
IL_000f: ldloc.2
IL_0010: ldc.i4.2
IL_0011: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0016: nop
IL_0017: ldloc.2
IL_0018: ldc.i4.3
IL_0019: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_001e: nop
IL_001f: ldloc.2
IL_0020: ldc.i4.4
IL_0021: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
IL_0026: nop
IL_0027: ldloc.2
IL_0028: stloc.0
IL_0029: nop
IL_002a: ldloc.0
IL_002b: ldsfld class [mscorlib]System.Func`2<int32,bool> ConsoleApplication1.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
IL_0030: brtrue.s IL_0045
IL_0032: ldnull
IL_0033: ldftn bool ConsoleApplication1.Program::'<Main>b__1'(int32)
IL_0039: newobj instance void class [mscorlib]System.Func`2<int32,bool>::.ctor(object,
native int)
IL_003e: stsfld class [mscorlib]System.Func`2<int32,bool> ConsoleApplication1.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
IL_0043: br.s IL_0045
IL_0045: ldsfld class [mscorlib]System.Func`2<int32,bool> ConsoleApplication1.Program::'CS$<>9__CachedAnonymousMethodDelegate2'
IL_004a: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>,
class [mscorlib]System.Func`2<!!0,bool>)
IL_004f: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
IL_0054: stloc.3
.try
{
IL_0055: br.s IL_0067
IL_0057: ldloc.3
IL_0058: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
IL_005d: stloc.1
IL_005e: nop
IL_005f: ldloc.1
IL_0060: call void [mscorlib]System.Console::WriteLine(int32)
IL_0065: nop
IL_0066: nop
IL_0067: ldloc.3
IL_0068: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_006d: stloc.s CS$4$0001
IL_006f: ldloc.s CS$4$0001
IL_0071: brtrue.s IL_0057
IL_0073: leave.s IL_0087
} // end .try
finally
{
IL_0075: ldloc.3
IL_0076: ldnull
IL_0077: ceq
IL_0079: stloc.s CS$4$0001
IL_007b: ldloc.s CS$4$0001
IL_007d: brtrue.s IL_0086
IL_007f: ldloc.3
IL_0080: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0085: nop
IL_0086: endfinally
} // end handler
IL_0087: nop
IL_0088: ret
} // end of method Program::Main
最佳答案
编译器将您的代码转换为 try-finally block ,首先它调用 GetEnumerator
源上的方法,(这是从 Where)
返回的迭代器,然后进入 try block 。
第一条指令:
IL_0055: br.s IL_0067
跳转到 IL_0067
调用MoveNext
在迭代器上,然后加载 MoveNext
的结果进入局部变量(正如奇怪的名字所暗示的那样(CS $ 4 $ 0001)这是一个编译器生成的变量):
IL_006d: stloc.s CS$4$0001
IL_006f: ldloc.s CS$4$0001
此指令检查结果是否从 MoveNext
返回是true
如果它跳回到IL_0057
IL_0071: brtrue.s IL_0057
然后继续执行,同样的操作一直运行到MoveNext
返回 false
.所以是的,代码中有一个循环。
您可以找到有关 IL
的更多信息documentation 中的说明.
除此之外try
之前的代码块可能看起来令人困惑,但它基本上创建了一个 Func<int, bool>
delegate 这是你的 lambda 表达式 ( x => x != 2
) 然后将它传递给 Where
方法。并将它的结果加载到 3.(实际上是第四个,3
是索引)这一行的局部变量:
IL_0054: stloc.3
这是一个IEnumerator<int>
正如您在参数列表中看到的那样。然后您的循环使用该迭代器。
关于c# - 一圈还是两圈? (如何阅读 IL),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27180212/