简体   繁体   English

Conditional 属性如何工作?

[英]How does the Conditional attribute work?

I have some helper methods marked with [Conditional("XXX")] .我有一些用[Conditional("XXX")]标记的辅助方法。 The intent is to make the methods conditionally compile when only the XXX conditional compilation symbol is present.目的是在仅存在 XXX 条件编译符号时使方法有条件地编译。 We're using this for debugging and tracing functionality and it works quite well.我们正在使用它来调试和跟踪功能,它运行良好。

During my research on how the conditional compilation works, I found several sources stating methods tagged with the Conditional attribute will be placed in the IL but calls to the methods will not be executed.在我研究条件编译如何工作的过程中,我发现有几个来源声明用Conditional属性标记的方法将放置在 IL 中,但不会执行对方法的调用。

How does code get compiled into IL but not executed?代码如何被编译成 IL 但不执行? How can I verify the behavior is actually as described?如何验证行为实际上与描述的一样? I haven't done much with IL so my skills are little weak in this area.我在 IL 方面做得不多,所以我在这方面的技能有点弱。

This is controlled by the compiler.这是由编译器控制的。 All methods with [Conditional] will still be included in the MSIL, but will include a .custom instance line that details the [Conditional] .所有带有[Conditional]方法仍将包含在 MSIL 中,但将包含一个.custom instance行,详细说明[Conditional] At compile time for a method caller the compiler lexes, then does semantic analysis and overload resolution, and finds the .custom instance IL in the method you placed [Conditional] on.在方法调用者的编译时,编译器进行.custom instance分析,然后进行语义分析和重载解析,并在您放置[Conditional]的方法中找到.custom instance IL。 It therefore doesn't compile the call.因此它不会编译调用。

So: the compiler compiles the target method, but does not compile any call to that method.所以:编译器编译目标方法,但不编译对该方法的任何调用 Note: the method is still there and you could still call it with reflection.注意:该方法仍然存在,您仍然可以通过反射调用它。 See the spec查看规格

Calls to a conditional method are either included or omitted depending on whether this symbol is defined at the point of the call.包含或省略对条件方法的调用取决于此符号是否在调用点定义。 If the symbol is defined, the call is included;如果定义了符号,则包括调用; otherwise, the call (including evaluation of the receiver and parameters of the call) is omitted.否则,调用(包括接收者的评估和调用的参数)将被省略。

How can you verify it?你怎么能验证呢? Fire up the developer command prompt, type ildasm <enter> and open the relevant dlls/exes.启动开发人员命令提示符,输入ildasm <enter>并打开相关的 dlls/exes。 Check out the caller and the called [Conditional] methods.检查调用者和被调用的[Conditional]方法。 You'll see the called methods have the extra IL with .custom instance , and the caller lines are omitted where you would expect.您将看到被调用的方法具有带有.custom instance的额外 IL,并且在您期望的地方省略了调用者行。 Try it on a console application with the code below.使用下面的代码在控制台应用程序上尝试一下。

Why?为什么? It makes conditional calls simpler in some cases than using #if .在某些情况下,它使条件调用比使用#if更简单。 See Eric Lippert: What's the difference between conditional compilation and the conditional attribute?参见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?");
    }
}

And in the corresponding MSIL, VerboseEmit is included, but not called from Main :在相应的 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

Bonus points.奖励积分。 Look at the console output and the MSIL for this (modify the Emit methods accordingly):查看控制台输出和 MSIL(相应地修改 Emit 方法):

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

( Have debated whether this qualifies as an answer or not, but feel it's worth mentioning. If people disagree with DVs or comments, I'll happily delete ) 一直在争论这是否可以作为答案,但觉得值得一提。如果有人不同意DV或评论,我会很乐意删除

One important feature of the fact that this affects call-sites and not the methods themselves is that this feature works across assemblies, and it's the compilation symbols that are in scope for the call site that affects whether the call is invoked.这会影响调用站点而不是方法本身这一事实的一个重要特征是此功能程序集工作,并且调用站点范围内的编译符号会影响调用是否被调用。

So one reason why the actual methods have to be emitted into the compiled assembly is because, at that time, it's not actually known whether the method is going to be invoked.因此,必须将实际方法发送到已编译程序集中的原因之一是,当时实际上并不知道是否要调用该方法。

It's at the later point in time, when the consuming application is being compiled, that we actually know whether the method is used or not.在稍后的时间点,当消费应用程序被编译时,我们才真正知道该方法是否被使用。 This also means that in a complex solution, where there are multiple consumers at multiple levels, some of the calls may happen (in some projects) and others will not.这也意味着在复杂的解决方案中,在多个级别有多个消费者的情况下,某些调用可能会发生(在某些项目中),而其他调用则不会发生。

If your conditional somehow does not work and it looks like this in VS:如果您的条件不知何故不起作用,并且在 VS 中看起来像这样:

在此处输入图片说明

Make sure you add a Debug compilation symbol in your project Build properties:确保在项目构建属性中添加调试编译符号:

在此处输入图片说明

You can use DEBUG instead in Debug in your Conditional as well and it would work.您也可以在 Conditional 中的 Debug 中使用 DEBUG 代替它,它会起作用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM