条件属性如何工作?

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

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

代码如何编译成IL而不执行? 如何validation行为实际上是如上所述? 我在IL方面做得不多,所以我的技能在这个方面都很弱。

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

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

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

你怎么validation它? 启动开发人员命令提示符,键入ildasm 并打开相关的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?"); } } 

在相应的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(); } 

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

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

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

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

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

在此处输入图像描述

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

在此处输入图像描述

您可以在条件中使用DEBUG代替Debug,它也可以使用。