Reputation: 128317
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."
Upvotes: 8
Views: 369
Reputation: 11
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 )));
Upvotes: 1
Reputation: 1000
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:
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
Upvotes: 3