【发布时间】:2016-01-08 13:35:02
【问题描述】:
我已经实现了一个 foreach 循环和一个 while 循环,它们应该创建几乎相同的 IL 代码。
IL 代码(使用 C#5 的编译器版本 12.0.40629 生成)确实几乎相同(某些数字等自然例外),但反编译器能够重现初始代码。
允许反编译器判断前一个代码块是 foreach 循环而后一个代码块代表一个 while 循环的关键区别是什么?
我在下面提供的反编译代码是使用 ILSpy (2.3.1.1855) 的最新版本(截至今天)生成的,但我也使用了 JustDecompile、.NET Reflector 和 dotPeek — 没有区别。我没有配置任何东西,我只是安装了工具。
原码:
using System;
using System.Collections.Generic;
namespace ForeachVersusWhile
{
public class Program
{
public static void Main(string[] args)
{
var x = new List<int> {1, 2};
foreach (var item in x)
{
Console.WriteLine(item);
}
using (var enumerator = x.GetEnumerator())
{
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
}
}
}
}
反编译代码:
List<int> x = new List<int>
{
1,
2
};
foreach (int item in x)
{
Console.WriteLine(item);
}
using (List<int>.Enumerator enumerator = x.GetEnumerator())
{
while (enumerator.MoveNext())
{
Console.WriteLine(enumerator.Current);
}
}
IL 代码(仅限循环):
[...]
IL_0016: ldloc.0
IL_0017: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
IL_001c: stloc.s CS$5$0000
.try
{
IL_001e: br.s IL_002e
// loop start (head: IL_002e)
IL_0020: ldloca.s CS$5$0000
IL_0022: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_0027: stloc.1
IL_0028: ldloc.1
IL_0029: call void [mscorlib]System.Console::WriteLine(int32)
IL_002e: ldloca.s CS$5$0000
IL_0030: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0035: brtrue.s IL_0020
// end loop
IL_0037: leave.s IL_0047
} // end .try
finally
{
IL_0039: ldloca.s CS$5$0000
IL_003b: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
IL_0041: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0046: endfinally
} // end handler
IL_0047: ldloc.0
IL_0048: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
IL_004d: stloc.2
.try
{
IL_004e: br.s IL_005c
// loop start (head: IL_005c)
IL_0050: ldloca.s enumerator
IL_0052: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_0057: call void [mscorlib]System.Console::WriteLine(int32)
IL_005c: ldloca.s enumerator
IL_005e: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0063: brtrue.s IL_0050
// end loop
IL_0065: leave.s IL_0075
} // end .try
finally
{
IL_0067: ldloca.s enumerator
IL_0069: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
IL_006f: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0074: endfinally
} // end handler
问题的背景:
我读过一篇文章,他们查看了 C# 代码被编译成的内容。在第一步中,他们查看了一个简单的示例:foreach 循环。
在MSDN 的支持下,foreach 循环应该“隐藏枚举器的复杂性”。 IL 代码对 foreach 循环一无所知。所以,我的理解是,在底层,foreach 循环的 IL 代码等于使用 IEnumerator.MoveNext 的 while 循环。
因为 IL 代码不代表 foreach 循环,所以反编译器很难判断使用了 foreach 循环。这引发了几个问题,人们想知道为什么在反编译自己的代码时会看到一个while循环。这是example。
我自己想看看,写了一个带有foreach循环的小程序并编译了它。然后我使用反编译器查看代码的样子。我没想到会有一个 foreach 循环,但当我真正得到一个时,我感到很惊讶。
纯 IL 代码自然包含对 IEnumerator.MoveNext 等的调用。
我想我做错了什么,因此使工具能够访问更多信息,从而正确地告诉我正在使用 foreach 循环。那么,为什么我看到的是 foreach 循环而不是使用 IEnumerator.MoveNext 的 while 循环?
【问题讨论】:
-
您依赖哪个来源让您假设创建了一个while循环?
-
在人们看到 while 循环的时候,反编译工具可能还不够聪明,现在他们变得更聪明了,可以识别 foreach 循环!顺便说一句,说他们找到了 while-loop 的消息来源在哪里。
-
在 IL 中没有
while循环这样的东西。只有条件分支......基本上,两块 C# 生成相同的 IL 是完全可行的。请注意,至少在 Reflector 中,您可以根据 C# 版本告诉它某种程度的“优化”——但我不知道这是否会影响foreach。 -
因为这些工具被设计 试图给您返回合理的 C# 代码?所以他们被教导识别编译器技巧并撤消它们?
-
@Em1:不能。是什么让你认为它可以?你能生成两个生成相同 IL 但被反编译回其原始形式的程序吗?如果是这样,那应该是问题所在。
标签: c# decompiling