A few months ago, I created a scalable data migration tool for a customer using Dataflow. I’ll promise I write a blog post about it when time permits.
The tool was originally created with some assumptions about your primary key strategy; in this case that an identity column was used. However this week I was asked if the tool could be used for another application. The only problem that for this application a sequential guid was used for primary key values.
I could make the full application configurable to handle multiple key strategies but as this was a one shot migration, I decided to use a different strategy and use conditional compilation symbols to handle this scenario.
Let me first explain what conditional compilation symbols are…
What Are Conditional Compilation Symbols?
Conditional compilation symbols in C# are essentially preprocessor directives that allow the compiler to include or omit portions of code based on certain conditions. These symbols are particularly useful when you need to control how different builds behave without changing the underlying codebase.
The common use cases include:
- Debug vs Release builds
- Platform-specific code (e.g., Windows vs macOS vs Linux)
- Experimental features or beta testing
- Configuration management (e.g., enabling/disabling logging)
You use these compilation symbols in combination with the following directives in C#:
#define
and#undef
: These allow you to define or undefine symbols within a file.#if
,#elif
,#else
, and#endif
: These are used to conditionally compile code based on whether a symbol is defined or not.
Let’s take a quick look at a basic example:
Here, when the DEBUG
symbol is defined, the first Console.WriteLine
statement will be included in the build. If it’s not defined, the second statement for release mode will be included instead.
Defining Conditional Compilation Symbols
You can define symbols in several places, making them flexible for various scenarios:
- In Code: As seen in the example above, you can define them directly in the code file using
#define
:
- Project Properties: This can be done directly in the csproj file:
or through the Visual Studio UI: Right click on your project -> Properties –> Build –> General:
- Command Line: When compiling with
dotnet build
orcsc
, you can pass symbols as command-line arguments using –p:DefineConstants:
dotnet build -p:DefineConstants="PLATTELANDSLOKET"
Each method offers flexibility depending on where and how you need to apply these symbols.
As I mentioned in the introduction I used the approach above to conditionally switch between the 2 primary key strategies. So you'll find multiple #if
, #else
,#endif
directives spread out through the codebase:
Some heuristics when using conditional compilation symbols
While conditional compilation is a powerful feature, it can also lead to code that is harder to maintain if overused. Here are some best practices to follow:
- Minimize Use: Only use conditional compilation where absolutely necessary. It can complicate code readability and maintainability. The example I shared was a good use case especially as the impact was limited.
- Keep Code Modular: Use conditional compilation at a high level (e.g., method or class) rather than for individual statements or lines. This keeps the logic clean and manageable.
- Document it: Ensure that your conditional symbols are well-documented, either in your code comments or your project's documentation, so team members know why certain code paths are being excluded or included. It introduces another level of magic so make sure that people are aware that these symbols are available and/or in use.
Happy coding!
More information
Preprocessor directives - C# reference | Microsoft Learn