"using" 和 "try...finally" 之间的 .NET 反编译器区别

.NET decompiler distinction between "using" and "try...finally"

给定以下 C# 代码,其中 Dispose 方法以两种不同的方式调用:

class Disposable : IDisposable
{
    public void Dispose()
    {
    }
}

class Program
{
    static void Main(string[] args)
    {
        using (var disposable1 = new Disposable())
        {
            Console.WriteLine("using");
        }

        var disposable2 = new Disposable();
        try
        {
            Console.WriteLine("try");
        }
        finally
        {
            if (disposable2 != null)
                ((IDisposable)disposable2).Dispose();
        }
    }
}

使用发布配置编译然后使用 ildasm 反汇编后,MSIL 如下所示:

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size       57 (0x39)
  .maxstack  1
  .locals init ([0] class ConsoleApplication9.Disposable disposable2,
           [1] class ConsoleApplication9.Disposable disposable1)
  IL_0000:  newobj     instance void ConsoleApplication9.Disposable::.ctor()
  IL_0005:  stloc.1
  .try
  {
    IL_0006:  ldstr      "using"
    IL_000b:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_0010:  leave.s    IL_001c
  }  // end .try
  finally
  {
    IL_0012:  ldloc.1
    IL_0013:  brfalse.s  IL_001b
    IL_0015:  ldloc.1
    IL_0016:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_001b:  endfinally
  }  // end handler
  IL_001c:  newobj     instance void ConsoleApplication9.Disposable::.ctor()
  IL_0021:  stloc.0
  .try
  {
    IL_0022:  ldstr      "try"
    IL_0027:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_002c:  leave.s    IL_0038
  }  // end .try
  finally
  {
    IL_002e:  ldloc.0
    IL_002f:  brfalse.s  IL_0037
    IL_0031:  ldloc.0
    IL_0032:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_0037:  endfinally
  }  // end handler
  IL_0038:  ret
} // end of method Program::Main

DotPeekJustDecompile 等 .NET 反编译器如何区分 using尝试...最后 ?

实际上并没有什么不同。正如马克在评论中所说 - 如果您编写的代码与编译器为 using 生成的代码相同 - 反编译器将无法发挥作用。

但是,包括 DotPeek 在内的许多反编译器实际上可以使用调试符号 (.pdb) 文件来定位实际源代码,然后使用实际源代码,因此根本不会发生反编译。此外,在调试模式下编译也可能会影响模式(即 - 您尝试模仿 using 语句可能在调试与发布编译中产生不同的结果 IL)。

要防止 DotPeek 使用您的真实源代码文件,请转至工具 > 选项 > 反编译器并取消选中 "Use sources from symbol files when available"。然后在 Release 中编译您的代码,观察 DotPeek 将两个语句反编译为 using.

How does a .NET decompiler such as DotPeek or JustDecompile make the difference between using and try...finally?

反编译器主要用于模式匹配。通常,IL 被翻译成目标语言(在本例中为 C#)中可能的最简单的等效表示。然后,该代码模型通过一系列转换,尝试将代码序列与众所周知的模式进行匹配。使用 ILSpy 的调试版本,您实际上可以查看此管道不同阶段的输出。

反编译器的管道可能包括像循环重写器这样的转换。循环重写器可能会通过查找 while 循环来重构 for 循环,这些循环之前有变量初始值设定项,并且在每个后沿之前还包含公共迭代语句。当检测到这样的循环时,它会被重写为更简洁的 for 循环。它 不知道 原始代码实际上包含一个 for 循环;它只是试图找到最简洁的方式来表示代码,同时保持正确性。

以类似的方式,using 重写器会查找 try/finally 块,其中 finally 包含简单的空检查和 Dispose() 调用,然后将它们重写为using 块,根据语言规范,它们更简洁,同时仍然是正确的。反编译器 不知道 代码包含 using 块,但由于几乎没有人使用显式 try/finally 形式,因此结果往往与原始来源。