开发者

Is ConditionalAttribute supposed to strip out entire lines, or just method calls?

According to the documentation on the ConditionalAttribute class:

Applying ConditionalAttribute to a method indicates to compilers that a call to the method should not be compiled into Microsoft intermediate language (MSIL) unless the conditional compilation symbol that is associated with ConditionalAttribute is defined.

To me this is saying that the Conditional attribute only alters behavior at the individual method call level. But consider the following code snippet:

class InstanceType
{
    public InstanceType DoSideEffects()
    {
        Console.WriteLine("Side effects!");
        return this;
    }

    public InstanceType DoMoreSideEffects()
    {
        Console.WriteLine("More side effects!");
        return this;
    }

    [Conditional("DEBUG")]
    public void ConditionalMethod()
    {
        Console.WriteLine("Conditional method run.");
    }
}

class Program
{
    static void Main()
    {
        var x = new InstanceType();

        // The compiler appears to strip out this entire line
        // in a Release build.
        x.DoSideEffects().DoMoreSideEffects().ConditionalMethod();

        var y = new InstanceType();

        // When each method call appears on its own line,
        // the first two methods are included as expected.
        y.DoSideEffects();
        y.DoMoreSideEffects();
        y.ConditionalMethod();
    }
}

Compare the outputs of Debug and Release builds:

DEBUG                    RELEASE
Side effects!            Side effects!
More side effects!       More side effects!
Conditional method run.
Side effects!
More side effects!
Conditional method run.

Is this behavior specified somewhere? I 开发者_JAVA技巧had thought that both builds were supposed to have the same output except for the lines reading "Conditional method run."


Interessting feature :-) I've never noticed that.

I've taken a look at the IL. This doesn't explain the behaviour (the compilation process), but it documents the result anyway, I believe.

The whole C# code line is clearly left out in the IL:

  • In the DEBUG compilation a new object is created (the x variable), stored at location 0 and loaded. Afterwards the three methods are applied successively: DoSideEffects(), DeMoreSideEffects(), and ConditionalMethod()
  • In the RELEASE compilation the variable still gets created, but since it is not needed, it is immediately pop'ed. Instead the y variable is stored at location 0 and loaded.

To me, this looks like a bug, really. It seems that it would have been possible to just exclude the ConditionalMethod() call in the IL. But it seems that you are right, that the whole line is left out.

// DEBUG compilation
.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       58 (0x3a)
  .maxstack  1
  .locals init (class ConsoleApplication3.InstanceType V_0,
           class ConsoleApplication3.InstanceType V_1)
  IL_0000:  nop
  IL_0001:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_0006:  stloc.0
  IL_0007:  ldloc.0
  IL_0008:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoSideEffects()
  IL_000d:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoMoreSideEffects()
  IL_0012:  callvirt   instance void ConsoleApplication3.InstanceType::ConditionalMethod()
  IL_0017:  nop
  IL_0018:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_001d:  stloc.1
  IL_001e:  ldloc.1
  IL_001f:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoSideEffects()
  IL_0024:  pop
  IL_0025:  ldloc.1
  IL_0026:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoMoreSideEffects()
  IL_002b:  pop
  IL_002c:  ldloc.1
  IL_002d:  callvirt   instance void ConsoleApplication3.InstanceType::ConditionalMethod()
  IL_0032:  nop
  IL_0033:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  IL_0038:  pop
  IL_0039:  ret
} // end of method Program::Main

// RELEASE compilation
.method private hidebysig static void  Main() cil managed
{
  .entrypoint
  // Code size       33 (0x21)
  .maxstack  1
  .locals init ([0] class ConsoleApplication3.InstanceType y)
  IL_0000:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_0005:  pop
  IL_0006:  newobj     instance void ConsoleApplication3.InstanceType::.ctor()
  IL_000b:  stloc.0
  IL_000c:  ldloc.0
  IL_000d:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoSideEffects()
  IL_0012:  pop
  IL_0013:  ldloc.0
  IL_0014:  callvirt   instance class ConsoleApplication3.InstanceType ConsoleApplication3.InstanceType::DoMoreSideEffects()
  IL_0019:  pop
  IL_001a:  call       valuetype [mscorlib]System.ConsoleKeyInfo [mscorlib]System.Console::ReadKey()
  IL_001f:  pop
  IL_0020:  ret
} // end of method Program::Main


Sorry drag up such an old post, but I just encountered the same thing and this was the only discussion of this issue that I could find.

I have a hunch as to what is going on. The [Conditional] is stripping the call to ConditionalMethod() as well as any expressions that act as parameters passed to it (as per the documentation, and the other thread linked above).

My guess is that the implicit this parameter is being treated exactly the same way. In the line x.DoSideEffects().DoMoreSideEffects().ConditionalMethod(); the expression that is passed as this is x.DoSideEffects().DoMoreSideEffects() which is dutifully stripped, eliminating the side effects.

If we rewrite into pseudo code where we explicitly pass this as the first parameter to each method it becomes much clearer:

ConditionalMethod( DoMoreSideEffects( DoSideEffects( x )));

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜