我有一些标记为[Conditional("XXX")]
的辅助方法,意图是在只有 XXX 条件编译符号存在时有条件地编译这些方法。我们将其用于调试和跟踪功能,它非常有效。
在我研究条件编译工作原理时,我发现几个来源称,使用Conditional
属性标记的方法将被放置在IL中,但是对这些方法的调用将不会被执行。
代码如何编译成IL但不会被执行呢?我该如何验证实际上行为与描述的一样?我在IL方面的技能还比较薄弱。
我有一些标记为[Conditional("XXX")]
的辅助方法,意图是在只有 XXX 条件编译符号存在时有条件地编译这些方法。我们将其用于调试和跟踪功能,它非常有效。
在我研究条件编译工作原理时,我发现几个来源称,使用Conditional
属性标记的方法将被放置在IL中,但是对这些方法的调用将不会被执行。
代码如何编译成IL但不会被执行呢?我该如何验证实际上行为与描述的一样?我在IL方面的技能还比较薄弱。
这由编译器控制。所有带有 [Conditional]
的方法仍会被包含在 MSIL 中,但会包括一个细节了 [Conditional]
的 .custom 实例
行。对于方法调用者,在编译时编译器进行词法分析、语义分析和重载解析,并在你使用 [Conditional]
的方法中找到 .custom 实例
IL。因此它不会编译该调用。
所以:编译器编译目标方法,但是不会编译任何对该方法的调用。注意:该方法仍然存在,你仍然可以使用反射调用它。参见规范
条件方法的调用要么包含要么省略,具体取决于该符号在调用点是否定义。如果定义了该符号,则调用将被包含;否则,该调用(包括接收方和调用参数的评估)将被省略。
如何验证?打开开发人员命令提示符,键入ildasm <enter>
并打开相关的dlls/exes。查看调用方和被调用的 [Conditional]
方法。你会看到被调用的方法具有额外的 IL 和 .custom 实例
,而在预期情况下省略了调用方的行。在控制台应用程序中尝试下面的代码。
为什么?在某些情况下,这使得条件调用比使用 #if
更简单。参见Eric Lippert: 条件编译和条件属性之间有什么区别?
class Program
{
static void Main(string[] args)
{
AlwaysEmit();
DebugEmit();
VerboseEmit();
}
public static void AlwaysEmit()
{
Console.WriteLine("Beam me up");
}
[Conditional("DEBUG")]
public static void DebugEmit()
{
Console.WriteLine("Kirk out");
}
[Conditional("VERBOSE")]
public static void VerboseEmit()
{
Console.WriteLine("Say that again?");
}
}
在相应的 MSIL 中,VerboseEmit
被包含了,但并没有从 Main
中调用:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
// Code size 14 (0xe)
.maxstack 8
IL_0000: nop
IL_0001: call void RateScope.SdrApi.UploaderConsoleApp.Program::AlwaysEmit()
IL_0006: nop
IL_0007: call void RateScope.SdrApi.UploaderConsoleApp.Program::DebugEmit()
IL_000c: nop
IL_000d: ret
} // end of method Program::Main
...
.method public hidebysig static void VerboseEmit() cil managed
{
.custom instance void [mscorlib]System.Diagnostics.ConditionalAttribute::.ctor(string)
= ( 01 00 07 56 45 52 42 4F 53 45 00 00 ) // ...VERBOSE..
// Code size 13 (0xd)
.maxstack 8
IL_0000: nop
IL_0001: ldstr "Say that again\?"
IL_0006: call void [mscorlib]System.Console::WriteLine(string)
IL_000b: nop
IL_000c: ret
} // end of method Program::VerboseEmit
加分项。查看控制台输出和此内容的中间语言(根据需要修改Emit方法):
static void Main(string[] args)
{
int callCount = 0;
AlwaysEmit(++callCount);
VerboseEmit(++callCount);
DebugEmit(++callCount);
Console.WriteLine("Call count = " + callCount);
Console.ReadLine();
}