将 IL 转换为 C#,没有语法糖

Converting IL to C# with no syntactic sugar

提问人:David Klempfner 提问时间:9/14/2019 更新时间:9/14/2019 访问量:438

问:

我正在寻找一个程序,该程序将向我显示给定 IL 代码的最低级别(即无语法糖)C# 代码。

我尝试使用 .NET Reflector 查看一个包含带有循环的简单控制台应用程序的 .exe 文件,希望看到 、 等,但它将其显示为循环。foreachGetEnumerator()MoveNext()Currentforeach

这样的程序存在吗?或者是否可以在 .NET Reflector 中选择“无语法糖”?

C# CIL .net-反射器

评论

0赞 ℍ ℍ 9/14/2019
Reflector 的目标是尽可能多地回收句法糖。您希望看到 lambda 或异步方法的什么?

答:

1赞 YellowAfterlife 9/14/2019 #1

ILSpy 的当前版本具有一组相当大的选项,用于启用/禁用反编译器转换功能:

...
        static void Main(string[] args) {
            foreach (var arg in args) {
                Console.WriteLine(arg);
            }
        }
...

enter image description here如果需要,你可以通过剥离逻辑来走得更远 和 ;也许可以提出一个问题,询问您的更改的 PR 是否会受到赞赏,因为这些“原始”设置中的大多数都是最近添加的。ICSharpCode.Decompiler.IL.Transforms.*ICSharpCode.Decompiler.CSharp.StatementBuilder


枚举器的更好示例

简洁的代码片段

var numbers = new List<int> { 0, 1, 2 };
foreach (var num in numbers) Console.WriteLine(num);

编译为

System.Collections.Generic.List<int> list = new System.Collections.Generic.List<int>();
list.Add(0);
list.Add(1);
list.Add(2);
System.Collections.Generic.List<int> numbers = list;
System.Collections.Generic.List<int>.Enumerator enumerator = numbers.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        int num = enumerator.Current;
        System.Console.WriteLine(num);
    }
}
finally
{
    ((System.IDisposable)enumerator).Dispose();
}

(如禁用所有转换设置时所示)


在for循环中:

就编译而言,与(除了放置 continue-label )相同,因此反编译器将根据 init-statement、condition 和 post-statement 之间的上下文相似性自由决定它可能是什么类型的循环,因此您甚至可以手动编写代码for (a; b; c) da; while (b) { d; c; }

var a = 0;
while (a < args.Length) {
    Console.WriteLine(args[a]);
    a++;
}

这将被检测为 for 循环(因为在 IL 中没有告知)

for (int a = 0; a < args.Length; a++)
{
    System.Console.WriteLine(args[a]);
}

评论

0赞 David Klempfner 9/14/2019
在您的屏幕截图中,它显示了一个 for 循环。但是在幕后,for 循环不用于 foreach,对吧?它使用 while 循环。
0赞 YellowAfterlife 9/14/2019
添加了更多示例来说明正在发生的事情 - 实际的可枚举对象将使用枚举器 + while-in-a-use(后者变为 try-finally);原始阵列上的 foreach 使用内联模式(可能是出于性能/冗余原因)
2赞 Theraot 9/14/2019 #2

YellowAfterlife 的评论中,OP 说:

在您的屏幕截图中,它显示了一个 for 循环。但是在幕后,for 循环不用于 foreach,对吧?它使用 while 循环。

循环访问数组时,它不使用枚举器对象。它改用整数索引计数器变量。你知道,就像一个for循环。IL 使用 和 ,我们可以说是“goto”。当然,如果你坚持,你可以把它写成一个循环。OpCodes.Br_SOpCodes.Blt_Swhile

为了测试,我写了以下代码:

static void Main(string[] args)
{
    var index = 0;
    while (index < args.Length)
    {
        var arg = args[index];
        Console.WriteLine(arg);
        index++;
    }
}

这是 ILSpy 的输出:

private static void Main(string[] args)
{
    for (int index = 0; index < args.Length; index++)
    {
        Console.WriteLine(args[index]);
    }
}

事实上,在 IL 中,检查是在循环之后移动的,并在开始时跳转到它。请记住,循环(与循环不同)应该在之前检查。请参阅 IL:whiledo ... while

// for (int i = 0; i < args.Length; i++)
IL_0000: ldc.i4.0
IL_0001: stloc.0
// (no C# code)
IL_0002: br.s IL_0010
// loop start (head: IL_0010)
    // Console.WriteLine(args[i]);
    IL_0004: ldarg.0
    IL_0005: ldloc.0
    IL_0006: ldelem.ref
    IL_0007: call void [mscorlib]System.Console::WriteLine(string)
    // for (int i = 0; i < args.Length; i++)
    IL_000c: ldloc.0
    IL_000d: ldc.i4.1
    IL_000e: add
    IL_000f: stloc.0

    // for (int i = 0; i < args.Length; i++)
    IL_0010: ldloc.0
    IL_0011: ldarg.0
    IL_0012: ldlen
    IL_0013: conv.i4
    IL_0014: blt.s IL_0004
// end loop

// (no C# code)
IL_0016: ret

注意哪个获取数组的长度。ldlen

您可以在 ShatpLab 上验证此代码

编译器正在优化对数组的访问。因此,我们可以争辩说编译器将我的循环变成了一个循环。whilefor