C# – Conditional compilation

You can exclude specific code from being compiled by using conditional compilation symbols.

There are a few predefined symbols that support common scenarios – conditional compilation based on target framework (ex: .NET 5 vs .NET Core 3.1) and based on build configuration (Debug vs Release). In addition, you can add your own symbols to handle any scenario you run into.

In this article, I’ll show how to conditionally compile code based on two common scenarios (target framework and build configuration), and then show how to add your own symbols to support a unique scenario.

Conditionally compile code based on the target framework

If you are targeting multiple frameworks, you may want to conditionally compile code based on the target framework.

For example, let’s say you’re multi-targeting .NET 5.0 and .NET Core 3.1. You can conditionally compile blocks of code depending on the framework like this:

	static void Main(string[] args)
	{
		Test();
		Console.ReadLine();
	}

#if NET5_0
	public static void Test()
	{
		Console.WriteLine("I'm running in .NET 5");
	}
#elif NETCOREAPP3_1
	public static void Test()
	{
		Console.WriteLine("I'm running in .NET Core 3.1");
	}
#endif
Code language: C# (cs)
  • Execute the .NET 5 executable in bin/Debug/net5.0/example.exe

This outputs:

I'm running in .NET 5Code language: plaintext (plaintext)
  • Execute the .NET Core 3.1 executable in bin/Debug/netcoreapp3.1/example.exe

This outputs:

I'm running in .NET Core 3.1Code language: plaintext (plaintext)

Conditionally compile code based on the build config (Debug vs Release)

Sometimes you’ll have code that you only want to include in a debug build. Usually only devs will use debug builds in a dev environment, and everyone else will use release builds. So you can include functionality in a debug build that should only be available to devs.

For example, let’s say you have a command line utility that lets the user enter a command. One of the commands is “Push the little red button.” There’s a recurring joke in movies where there’s a little red button, and one of the characters says to the other “Just don’t push the little red button.”

Instead of asking users to not execute this command, just compile it out if it’s not a debug build, like this:

static void Main(string[] args)
{
	Console.WriteLine("Commands:");
	Console.WriteLine("1 - Send a request");
	Console.WriteLine("2 - Check for new messages");
#if DEBUG
	Console.WriteLine("3 - Push the little red button");
#endif

	var command = Console.ReadLine();

	switch (command)
	{
		case "1":
			SendRequest();
			break;
		case "2":
			CheckForNewMessages();
			break;
#if DEBUG
		case "3":
			PushTheLittleRedButton();
			break;
#endif 
	}
}
Code language: C# (cs)

When someone uses the release build, the “Push the little button red” command will be compiled out, and they’ll only see the other options:

Commands:
1 - Send a request
2 - Check for new messagesCode language: plaintext (plaintext)

Adding your own conditional compilation symbols

You can add any conditional compilation symbol you want. You can add them at the project level or file level. I’ll show examples of both ways to add them below.

First, here’s some code that uses a custom conditional compilation symbol called INCLUDE_SLOW_TESTS. Let’s say you have slow unit tests and sometimes you want to exclude them during development. In other words, you want to include the slow tests if it’s a release build or if the INCLUDE_SLOW_TESTS symbol is defined.

[TestClass()]
public class TestThings
{
	[TestMethod()]
	public void FastTest1()
	{
		//fast test 1
	}
#if !DEBUG || INCLUDE_SLOW_TESTS
	[TestMethod()]
	public void SlowTest1()
	{
		//slow test 1
	}
#endif
	[TestMethod()]
	public void FastTest2()
	{
		//fast test 1
	}
#if !DEBUG || INCLUDE_SLOW_TESTS
	[TestMethod()]
	public void SlowTest2()
	{
		//slow test 1
	}
#endif
}

Code language: C# (cs)

In most cases, you’ll want to actually disable the problematic tests by using the Ignore attribute.

Adding conditional compilation symbols at the project level

It’s a good idea to add the conditional compilation symbol at the project level if you’re using it in multiple files.

  • Right-click the project > Properties.
  • Go in the Build tab.
  • Select the Configuration and Platform you want to define the symbol for.
  • In the Conditional compilation symbols textbox, put the symbols you want to define (if you have more than one, separate them with a semicolon).
Project properties > Build tab > Specify the custom conditional compilation symbols you want and Configuration/Platform you want it to apply to

Take a look in the .csproj file, and notice that it added DefineConstants property:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>

    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
    <DefineConstants>TRACE;INCLUDE_SLOW_TESTS</DefineConstants>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
    <PackageReference Include="MSTest.TestAdapter" Version="2.1.1" />
    <PackageReference Include="MSTest.TestFramework" Version="2.1.1" />
    <PackageReference Include="coverlet.collector" Version="1.3.0" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\ConditionalCompliation\ConditionalCompliation.csproj" />
  </ItemGroup>

</Project>

Code language: HTML, XML (xml)

Note: You can add these constants to multiple shared property files.

If you’ve defined a symbol at the project level, and you want to undefine it for a single file, you can undefine it in that file:

#undef INCLUDE_SLOW_TESTS
Code language: C# (cs)

Adding conditional compilation symbols at the file level

If you’re only using a symbol in a single file, then you can define the symbol in the file itself, like this:

#define INCLUDE_SLOW_TESTS

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ConditionalCompliation.Tests
{
    [TestClass()]
    public class TestItTests
    {
        [TestMethod()]
        public void FastTest1()
        {
            //fast test 1
        }
#if !DEBUG || INCLUDE_SLOW_TESTS
        [TestMethod()]
        public void SlowTest1()
        {
            //slow test 1
        }
#endif

//the rest of the code
Code language: C# (cs)

To undefine the symbol:

  • Comment out the define line:
//#define INCLUDE_SLOW_TESTS
Code language: C# (cs)
  • or undefine it:
#undef INCLUDE_SLOW_TESTS
Code language: C# (cs)