【问题标题】:How does the Conditional attribute work?条件属性如何工作?
【发布时间】:2014-07-12 21:46:40
【问题描述】:

我有一些用[Conditional("XXX")] 标记的辅助方法。目的是在仅存在 XXX 条件编译符号时使方法有条件地编译。我们将其用于调试和跟踪功能,并且效果很好。

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

代码如何编译到 IL 中但不执行?我如何验证行为实际上与描述的一样?我在 IL 方面做得不多,所以我在这方面的技能有点薄弱。

【问题讨论】:

    标签: c# .net .net-4.5 il conditional-compilation


    【解决方案1】:

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

    所以:编译器编译目标方法,但不编译对该方法的任何调用。注意:该方法仍然存在,您仍然可以通过反射调用它。见the spec

    根据是否在调用点定义此符号来包含或省略对条件方法的调用。如果定义了符号,则包含调用;否则,调用(包括接收方的评估和调用的参数)将被省略。

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

    为什么?在某些情况下,它使条件调用比使用#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();
    }
    

    【讨论】:

      【解决方案2】:

      已经讨论过这是否可以作为答案,但觉得值得一提。如果人们不同意 DV 或 cmets,我很乐意删除

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

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

      在稍后的时间点,当正在编译消费应用程序时,我们才真正知道该方法是否被使用。这也意味着在一个复杂的解决方案中,在多个级别有多个消费者,一些调用可能会发生(在某些项目中)而其他调用不会发生。

      【讨论】:

        【解决方案3】:

        如果你的条件以某种方式不起作用并且在 VS 中看起来像这样:

        确保在项目的 Build 属性中添加 Debug 编译符号:

        您也可以在 Conditional 中的 Debug 中使用 DEBUG,它会起作用。

        【讨论】:

          猜你喜欢
          • 2014-01-13
          • 1970-01-01
          • 2011-02-10
          • 2011-07-10
          • 2012-04-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-29
          相关资源
          最近更新 更多