Eliminating #if Preprocessor Directives

Maybe it’s just me, maybe I’m old school, or maybe I’m not old school enough. In my opinion using the #if preprocessor directive should be a last resort, not a first choice. Consider the scenario where you are working on a project as part of a team. You create a class with a method that does something useful…and then people start coding against it. Of course, they trust that you’ve provided a stable implementation and they don’t need to review your code prior to calling it. So maybe your implementation looks something like this…

So your colleague came along and started using this new utility and finding it very helpful. His implementation might look something like this…

The code compiles and tests out GREAT! It passes through the CI (continuous integration) process just fine, and even makes it through QA without any problem. Then the code is built for release…and that configuration doesn’t define the DEBUG directive. So what happens to the code now?

  • AwesomeProgram.cs: error CS1501: No overload for method ‘DoSomethingUseful’ takes 2 arguments

Now your colleagues’ code has broken the build that everyone thought was solid through QA. That call to DoSomethingUseful should have been written to only pass the second parameter #if DEBUG, but how was your colleague going to know that? There was no commenting that the IDE could recognize to warn the user that the ‘value’ parameter is conditional, the parameter name didn’t indicate that it was only present #if DEBUG, so short of NOT trusting your implementation and reading every method you provide (which the whole team will be doing now) there was no way for anyone to know. All of that embarrassment, trouble, and the resulting mistrust of your code could have been avoided.

Another, even more painful scenario, is one that the compiler cannot catch for you and can very easily make it into production. The over simplified example below shows how misusing preprocessor directives can easily cause you trouble in production, the code compiles perfectly with or without the preprocessor symbol DEBUG being defined. This could easily be a much more important or critical bit of code inside the #if, here it’ll just save us from a null reference exception when DEBUG is defined and introduce a possible null reference exception in production.

In this example, the directive simply should not have been used. Perhaps a better example of a possible proper use for #if would be…

…at least now we can see the clear benefit in the alternate code. If debug then make sure we have a string value for some reason, otherwise return early because there’s no reason to continue.

How can you avoid this using a ConditionalAttribute?

With the ConditionalAttribute we get a few of benefits. Depending on your particular situation you may be able to get other benefits.

  1. All of the code, always exists and always has to compile.
  2. All of the code can be seen by the editor for advanced actions like refactoring and symbol renames.
  3. Even the debug scenario could operate like the release scenario without modifying anything other than the debug method body.

How is this ok for release? Well simply put, the compiler will turn the DebugFixUpString method into a no-op, so that any runtime code which would have called it is now just calling no-op.

Here’s what the reflected code looks like for a release code of the ConditionalAttribute example above.

You can clearly see that our conditional method still exists, but you can also clearly see that the compiler has removed the call to it because the DEBUG symbol was not defined at compile time.

Another common scenario for preprocessor directives is compatibility. Consider the situation where you’re creating an SDK and providing functionality supporting multiple .Net Framework versions. You might be thinking, this is a place for #if for sure! Not really. We can still avoid #if in multi-version targeting pretty easily.

Using a simple project structure and partial classes we can target different .Net Frameworks with differing framework functionality and underlying implementation, without affecting the eventual implementation details. Here’s a project structure example where a .Net Framework 4.0 project was created, and then backward compatibility was introduced in a .Net Framework 3.5 project which simply links in all the 4.0 project files and splits the classes with compatibility issues into partial classes to provide alternate implementations for .Net Framework 3.5 targets.

Preprocessor Directives Project Structure

The BestUtilityEver class is now split between three files. One common file in which the implementation is compatible with both framework versions, one v3.5 file for the v3.5  specifics and one v4.0 file with the v4.0 specifics. All three files form one single class at runtime and provide all the required signatures for the consuming implementations. Here’s what those files look like.


So, how do you avoid #if in your code?

  • Don’t use preprocessor directives
    Figure out why you have this variation and try to design it out of your solution, if you can’t try some other mechanism, possibly the ConditionalAttribute or something like it, that the compiler can understand to gracefully handle variations.
  • Know your tools
    Know what your development tools are capable of, and what your compiler is capable of.
  • Comment your code
    If you exhaust all other options and have to use preprocessor directives then comment your code in a way that the IDE your team uses can understand so other team members at least have the chance to understand that they need to pay more attention than usual to this particular implementation. Give your colleagues a fighting chance to not break the build.

While I was writing this, a colleague of mine sent me a link to a case study titled “#ifdef Confirmed Harmful: Promoting Understandable Software Variation” asserting that preprocessor directives are actually harmful to managing software variation. It was a very interesting read, have a look for yourself. What are your thoughts and experiences on preprocessor directives? Am I right, am I wrong, have I just gone completely off the deep end? Like I said in the beginning; maybe it’s just me, maybe I’m old school, or maybe I’m not old school enough, but I want to know what you think.

You’re infinitely more likely to find a better solution if you look for one.