"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
DotPeek 或 JustDecompile 等 .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
形式,因此结果往往与原始来源。
给定以下 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
DotPeek 或 JustDecompile 等 .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
形式,因此结果往往与原始来源。