Adam's blog formerly game development, now just casual madness

Conditional Details

If you’ve programmed in C# for a while, you’ve probably stumbled upon the Conditional attribute. It allows you to specify that a certain method will only be called when a certain compilation symbol is defined. Let’s check the details of that.

The Basics

The following method is a straightforward use case for the Conditional attribute. Let’s use it to get on the same page before proceeding:

public class Program
{
	public static void Main(string[] args)
	{
		Console.WriteLine("Begin");
		Foo();
		Console.WriteLine("End");
	}
	
	// Will only be called in Debug mode
	[Conditional("DEBUG")]
	public static void Foo()
	{
		Console.WriteLine("Foo has been called");
	}
}

We have defined a special method here that will only be invoked when compiling the program with the DEBUG compilation symbol defined, e.g. when compiling in Debug mode. If we compile a Release build, all invocations of Foo  will be removed entirely, leaving a zero footprint. This is quite useful for writing debug code, especially when developing a library for others to use:

Unlike an #if  / #endif block, the Conditional attribute is not evaluated when compiling the method itself, but when compiling the method call. If we pack Foo into a library, we can build it in Release mode and still call it from a program that is compiled in Debug. It’s actually the way the System.Diagnostics.Debug class is implemented.

Conditional Attribute OR

As it turns out, you can actually define multiple Conditional attributes on the same method:

[Conditional("DEBUG"), Conditional("TEST")]
public static void Foo()
{
	Console.WriteLine("Foo has been called");
}

What happens is that the method will now be called when either of those compiler symbols is present. Very useful if you want to allow your users to manually activate a Debug-only diagnostic feature in Release builds for testing purposes.

Compiler Directives

You may have guessed it, but you can also fiddle with the call behavior on a per-file basis using #define :

#define TEST

public class Program
{
	public static void Main(string[] args)
	{
		Console.WriteLine("Begin");
		Foo();
		Console.WriteLine("End");
	}
	
	[Conditional("TEST")]
	public static void Foo()
	{
		Console.WriteLine("Foo has been called");
	}
}

Conditional Parameters

This is where it gets interesting. Of course, if you pass a primitive parameter to an omitted Conditional method, that parameter should vanish as well. But what happens when we invoke a method, query a property or create an object in order to pass the resulting value as a parameter?

public class Program
{
	public static string SomeProperty
	{
		get
		{
			Console.WriteLine("SomeProperty getter called");
			return "SomeProperty";
		}
	}
	public static string SomeMethod()
	{
		Console.WriteLine("SomeMethod called");
		return "SomeMethod";
	}
	public class SomeClass
	{
		public SomeClass()
		{
			Console.WriteLine("SomeClass instantiated");
		}
	}

	public static void Main(string[] args)
	{
		Console.WriteLine("Begin");
		Foo(SomeProperty, SomeMethod(), new SomeClass());
		Console.WriteLine("End");
	}
	
	[Conditional("TEST")]
	public static void Foo(object a, object b, object c)
	{
		Console.WriteLine("Foo has been called with {0}, {1}, {2}", a, b, c);
	}
}

Since we’re triggering state changes here, it’s not safe to omit calculating these parameters, right?

Begin
End

But apparently, that’s exactly what happens. This is not a quirky compiler optimization: The Conditional attribute, when inactive, acts as if the whole method call didn’t exist at all – including its arguments. I personally wouldn’t have expected this, but it makes sense when you think about it in the context of Debug.Assert(..)  where it would be a waste of resources to calculate all the assertions despite not acting on them anyway.

Similarly, when using an anonymous delegate or lambda exclusively as parameter to an omitted Conditional method, it will be optimized away in Release builds. So even in these cases – at least as far as I observed it – there will likely be zero overhead.

Reflection

What happens when using Reflection to invoke a Conditional method?

public class Program
{
	public static void Main(string[] args)
	{
		Console.WriteLine("Begin");
		var method = typeof(Program).GetMethod("Foo");
		method.Invoke(null, null);
		Console.WriteLine("End");
	}
	
	[Conditional("TEST")]
	public static void Foo()
	{
		Console.WriteLine("Foo has been called");
	}
}

As intuition has probably told you: It will be invoked as ordered. Nothing escapes Reflection.

Delegates

One last corner case to wrap it up:  Will a delegate of the method patch through, even if the Conditionals compiler symbol is not defined?

public class Program
{
	public static void Main(string[] args)
	{
		Console.WriteLine("Begin");
		var action = new Action(Foo);
		action();
		Console.WriteLine("End");
	}
	
	[Conditional("TEST")]
	public static void Foo()
	{
		Console.WriteLine("Foo has been called");
	}
}

Nope. Because it won’t even compile. You’re not allowed to pack Conditional methods into delegates.

15 of 74