Conditional属性如何工作?

Den*_*ail 8 .net c# il conditional-compilation .net-4.5

我有一些标有的辅助方法[Conditional("XXX")].目的是在仅存在XXX条件编译符号时使方法有条件地编译.我们使用它来调试和跟踪功能,它运行良好.

在我研究条件编译如何工作的过程中,我发现有几个来源说明用Conditional属性标记的方法将放在IL中,但是不会执行对方法的调用.

代码如何编译成IL而不执行?如何验证行为实际上是如上所述?我没有对IL做过多少工作,所以我的技能在这个方面都很弱.

And*_*own 12

这由编译器控制.所有带有的方法[Conditional]仍将包含在MSIL中,但将包含.custom instance详细说明的行[Conditional].在编译器编译器的编译时编译器,然后进行语义分析和重载解析,并.custom instance在您放置的方法中找到IL [Conditional].因此它不编译调用.

所以:编译器编译目标方法,但不编译对该方法的任何调用.注意:该方法仍然存在,您仍然可以使用反射调用它.请参阅规格

根据是否在调用点定义了此符号,可以包含或省略对条件方法的调用.如果定义了符号,则包括呼叫; 否则,省略呼叫(包括呼叫参数的评估).

你怎么验证它?启动开发人员命令提示符,键入ildasm <enter>并打开相关的dll/exes.检查调用者和被调用的[Conditional]方法.您将看到被调用的方法具有额外的IL .custom instance,并且调用者行被省略在您期望的位置.使用下面的代码在控制台应用程序上尝试它.

为什么?在某些情况下,它使条件调用比使用更简单#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?");
    }
}
Run Code Online (Sandbox Code Playgroud)

并且在相应的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
Run Code Online (Sandbox Code Playgroud)

奖励积分.查看控制台输出和MSIL(相应地修改Emit方法):

static void Main(string[] args)
{
    int callCount = 0;
    AlwaysEmit(++callCount);
    VerboseEmit(++callCount);
    DebugEmit(++callCount);
    Console.WriteLine("Call count = " + callCount);
    Console.ReadLine();
}
Run Code Online (Sandbox Code Playgroud)


Dam*_*ver 7

(争论这是否有资格作为答案,但感觉值得一提.如果人们不同意DV或评论,我会高兴地删除)

影响调用站点而不是方法本身的一个重要特征是此功能适用程序集,并且它是调用站点范围内的编译符号,它影响调用是否被调用.

因此,必须将实际方法发送到已编译的程序集中的一个原因是,在那时,实际上并不知道是否要调用该方法.

在编译消费应用程序的时候,我们实际上知道该方法是否被使用.这也意味着在复杂的解决方案中,多个级别有多个消费者,某些调用可能会发生(在某些项目中),而其他调用则不会.