views:

51

answers:

1

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 had thought that both builds were supposed to have the same output except for the lines reading "Conditional method run."

+1  A: 

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
Maate