我有一些标记为[Conditional("XXX")]的助手方法。其目的是使方法在只存在xxx条件编译符号时进行有条件编译。我们使用它来调试和跟踪功能,它工作得很好。
在我研究条件编译工作原理的过程中,我发现有几个声明带有Conditional属性的方法的源将被放在il中,但是对这些方法的调用将不会被执行。
如何将代码编译成IL但不执行?如何验证行为是否如所述?我没有做太多的IL,所以我的技能在这方面有点薄弱。

最佳答案

这是由编译器控制的。带有[Conditional]的所有方法仍将包含在msil中,但将包含一行.custom instance来详细说明[Conditional]。在方法调用程序的编译时,编译器会分析语法,然后进行语义分析和重载解析,并在放置.custom instance的方法中找到[Conditional]il。因此它不会编译调用。
所以:编译器编译目标方法,但不编译对该方法的任何调用。注意:方法仍然存在,您仍然可以通过反射调用它。见the spec
对条件方法的调用是包含还是省略取决于此符号是否在调用点定义。如果定义了符号,则包含调用;否则,将忽略调用(包括对调用参数的求值)。
你怎么能证实?启动developer命令提示符,键入ildasm <enter>并打开相关的dlls/exes。查看调用方和被调用的[Conditional]方法。您将看到被调用的方法有额外的il和.custom instance,调用行在您期望的地方被省略。在控制台应用程序上使用下面的代码进行尝试。
为什么?在某些情况下,它使条件调用比使用#if更简单。见Eric Lippert: What's the difference between conditional compilation and the conditional attribute?

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

加分。查看控制台输出和msil(相应地修改emit方法):
static void Main(string[] args)
{
    int callCount = 0;
    AlwaysEmit(++callCount);
    VerboseEmit(++callCount);
    DebugEmit(++callCount);
    Console.WriteLine("Call count = " + callCount);
    Console.ReadLine();
}

10-08 14:40