Today’s article continues the series by looking at C#’s preprocessor support, which is like an expanded version of AS3’s compile-time constants and conditional compilation. Read on to learn about all the strange (and powerful) #something lines you can put in your code.

Table of Contents

To recap AS3’s support for compile-time constants, consider compiling some code like this:

mxmlc -define MATH::pi,"3.14159265" MyApp.as

That’ll define a compile-time constant called MATH::pi which you can use like this:

function circleArea(radius:Number): Number
{
	return MATH::pi * radius * radius;
}

The MATH::pi value is not a variable. Instead, the compiler replaces it with whatever text you passed on the command line for -define. This means that MATH::pi gets replaced in the source code with 3.14159265 before the file is actually compiled. What gets compiled looks like this:

function circleArea(radius:Number): Number
{
	return 3.14159265 * radius * radius;
}

Since this happens before the code is compiled, we call it a “preprocessor” step. In AS3, it can also be used to conditionally remove blocks of code like so:

// Only hide the context menu's built-in items in release builds
CONFIG::release {
	var menu:ContextMenu = new ContextMenu();
	menu.hideBuiltInItems();
	this.contextMenu = menu;
}

The system, unfortunately, can do little more than this so the coverage ends here. C#, however, has more functionality. Let’s start with defining compile-time constants. C# already has const variables that take care of cases like the MATH::pi above. However, your code can define a boolean value like CONFIG::release above using #if:

#if RELEASE

This can then be used by a #if/#endif pair to check the value:

#if RELEASE
	Debug.Log("Running in release mode");
#endif

If RELEASE is defined by a #define or in the build settings, the Debug.Log line will be compiled. Otherwise, it get stripped out just like with the AS3 conditional compilation.

As you might have guessed, there is a #else that works just like the normal else:

#if RELEASE
	Debug.Log("Running in release mode");
#else
	Debug.Log("Running in debug mode");
#endif

There is also a #elif:

#if RELEASE
	Debug.Log("Running in release mode");
#elif QA
	Debug.Log("Running in QA mode");
#else
	Debug.Log("Running in debug mode");
#endif

The #if and #elif directives can contain arbitrary boolean logic, just like the normal if:

#if (RELEASE == true && !QA)
	Debug.Log("Release and not QA");
#endif

Another trick is that you can conditionally define (with #if) and un-define (with #undef) these flags:

#define RELEASE
 
#if RELEASE
	// Release mode doesn't show FPS
	// Un-define in case it was ever defined
	#undef SHOWFPS
#else
	// Debug mode shows FPS
	// Define it in case it wasn't defined before
	#define SHOWFPS
#endif
 
// Show FPS if we're supposed to
// Note: don't need to know if this is based on debug/release
#if SHOWFPS
	Debug.Log("FPS: " + curFPS);
#endif

One special case about the #define and #undef directives is that they must occur at the beginning of the file. You can have comments beforehand, but no other code. The #if, #elif, and #endif directives can occur anywhere else in the rest of the file, just like the rest of the preprocessor directives.

Speaking of other directives, let’s start with #error. This generates a compile-time error with a custom message:

#define RELEASE
#define SHOWFPS
 
#if (RELEASE && SHOWFPS)
	#error Release can't show FPS
#endif

Or if you think an error is too harsh, you can use a #warning to generate a compile-time warning instead:

#define RELEASE
#define SHOWFPS
 
#if (RELEASE && SHOWFPS)
	#warning Release shouldn't show FPS
#endif

Another kind of directive is #pragma, which tells the compiler to do something compiler-specific. For example, Microsoft’s C# compiler supports #pragma warning disable X to disable compiler warning X and #pragma warning restore X to restore it. Think of these like non-standard extensions to the language which may or may not work on specific compilers.

The next directive is #line, which allows you to tell the compiler to change the line numbering and file name for the purposes of debugging. Here’s how that works:

int Add(int a, int b)                   // line: 1, file: Add
{                                       // line: 2, file: Add
	#line 100 "ThatFileWithAddInIt"
		int sum = a + b;        // line: 100, file: ThatFileWithAddInIt
		return sum;             // line: 101, file: ThatFileWithAddInIt
	#line default
}                                       // line: 7, file: Add

You can also use #line to hide particular lines from the debugger:

Debug.Log("this line can be debugged);
 
#line hidden
	Debug.Log("this line can NOT be debugged);
 
Debug.Log("this line can be debugged);

Finally, you can use #region and #endregion to mark off areas of the file. This is commonly respected by text editors and IDEs (e.g. Visual Studio, MonoDevelop) by collapsing the document’s regions using code folding. For example, it’s common to use regions to segment large files or sections of files:

#region Usings
	using System;
	using System.Collections.Generic;
	using System.Linq;
#endregion
 
public struct Vector2
{
	#region Variables
		public float X;
		public float Y;
	#endregion
 
	#region Constructors
		public Vector2(float uniform)
		{
			X = uniform;
			Y = uniform;
		}
 
		public Vector2(float x, float y)
		{
			X = x;
			Y = y;
		}
	#endregion
 
	#region Add functions
		public float Add(Vector2 vec)
		{
			X += vec.x;
			Y += vec.y;
		}
 
		public float Add(float val)
		{
			X += val;
			Y += val;
		}
	#endregion
}

Regions that are collapsed with code folding can yield a nice table-of-contents-style overview of the file that can be drilled into by expanding just the region you’re interested in.

That wraps up today’s coverage of the preprocessor in C#. The following side-by-side comparison shows the differences between it and the closest relative in AS3: compile-time definitions.

////////
// C# //
////////
// Define compile-time constant
#define DEBUG
 
#if (DEBUG || QA)
	#define SHOWFPS
#elif TESTING
	#define SHOWFPS
#else
	// Undefine compile-time constant
	#undef SHOWFPS
#endif
 
public class FramerateDisplayer
{
	public void Display(float rate)
	{
		// Only include if defined by preprocessor
		#if SHOWFPS
			#if RELEASE
				// Trigger compile-time error
				#error Can't show FPS in release
			#endif
 
			#if QA
				// Trigger compile-time warning
				#warning Shouldn't show FPS in QA
			#endif
 
			Debug.Log("FPS: " + rate);
		#endif
	}
}
 
// Hide next line from the debugger
#line hidden
	int result = 123 + 456;
 
// Change line numbering and file name
#line 100 "SomeOtherFile"
	int masked = 123 + 456;
 
// Restore line numbering
#line default
 
// Trigger compiler-specific functionality
#pragma warning disable 12345
float x = 3.14;
 
// Mark a region of the file
#region Usings
	using System;
	using System.Collections.Generic;
	using System.Linq;
#endregion
/////////
// AS3 //
/////////
 
// Define compile-time constant
// {only in build settings}
 
 
 
 
 
 
	// Undefine compile-time constant
	// {impossible in AS3}
 
 
public class FramerateDisplayer
{
	public function display(rate:Number): void
	{
		// Only include if defined by preprocessor
		SETTINGS::SHOWFPS {
			SETTINGS::RELEASE {
				// Trigger compile-time error
				// {impossible in AS3}
			}
 
			SETTINGS::QA {
				// Trigger compile-time warning
				// {impossible in AS3}
			}
 
			trace("FPS: " + rate);
		}
	}
}
 
// Hide next line from the debugger
// {impossible in AS3}
 
// Change line numbering and file name
// {impossible in AS3}
 
 
// Restore line numbering
// {impossible in AS3}
 
// Trigger compiler-specific functionality
// {impossible in AS3}
 
 
// Mark a region of the file
// {impossible in AS3}
 
 
 
//

That’s all for today. Stay tuned for next week when we’ll continue the series with even more exciting new features in C#!

Continue to Part 21

Spot a bug? Have a question or suggestion? Post a comment!