C#idiomatic C#
preprocessor-directivescsharp/fundamentals/program-structure

Language

Preprocessor directives

Learn how to use the most common C# preprocessor directives—conditional compilation, regions, and warning suppression—in everyday development.

  • page csharp/fundamentals/program-structure/preprocessor-directives
  • updated 03/16/2026

C# preprocessor directives tell the compiler what code to include, exclude, or treat differently when it builds your app. This guidance can change the resulting program. Preprocessor directives always start with # and must appear on their own line (ignoring leading whitespace). You can add a trailing comment after the directive. While the language reference documents all available directives, three groups cover everyday use:

  • File-based apps (#:) - configure file-based apps.
  • Conditional compilation (#if / #elif / #else / #endif) — include or exclude code based on build configuration or target framework.
  • Warning suppression (#pragma warning) — suppress or restore specific compiler warnings.

File-based app directives

Starting with C# 14, file-based apps use two additional directives:

  • #! — the shebang line that enables executing the file directly on Unix (for example, ./Program.cs). This requires the execute permission to be set on the file (chmod +x <file>).
  • #: — build-system directives that configure packages, SDK settings, and other options for single-file programs.

Use #:package to add a NuGet package. For example, the following file-based app uses the Spectre.Console package to render styled output:

csharp
#!/usr/bin/env dotnet
#:package Spectre.Console@*

AnsiConsole.MarkupLine("[bold green]Hello[/] from a file-based app!");

You can specify an exact version with @, or use @* to pull the latest version. Add multiple #:package directives to include more packages:

csharp
#:package Serilog@3.1.1

Other #: directives let you reference projects, set MSBuild properties, or change the SDK:

csharp
#:project ../SharedLibrary/SharedLibrary.csproj
#:property PublishAot=false
#:sdk Microsoft.NET.Sdk.Web

For the full list of directives, see File-based apps and the language reference.

Conditional compilation

Use #if, #elif, #else, and #endif to include or exclude code based on whether a symbol is defined. The most common symbols are DEBUG (set automatically for Debug builds) and target framework symbols like NET10_0_OR_GREATER:

sample · csharpdocs/csharp/fundamentals/program-structure/snippets/preprocessor-directives/Program.csConditionalCompilation

csharp
// <ConditionalCompilation>
static void ConfigureLogging()
{
#if DEBUG
    Console.WriteLine("Debug logging enabled — verbose output active.");
#else
    Console.WriteLine("Release logging — errors only.");
#endif
}
// </ConditionalCompilation>

// <CombinedConditions>
static void ShowPlatformInfo()
{
#if NET10_0_OR_GREATER
    Console.WriteLine("Running on .NET 10 or later.");
#elif NET8_0_OR_GREATER
    Console.WriteLine("Running on .NET 8 or 9.");
#else
    Console.WriteLine("Running on an older .NET version.");
#endif
}
// </CombinedConditions>

ConfigureLogging();
ShowPlatformInfo();

The build system defines the DEBUG symbol when you build in the Debug configuration. You don't need to define it yourself. Target framework symbols like NET10_0_OR_GREATER and NET8_0_OR_GREATER let you write code that adapts to different .NET versions in multi-targeting projects.

You can combine symbols with logical operators: && (and), || (or), and ! (not):

sample · csharpdocs/csharp/fundamentals/program-structure/snippets/preprocessor-directives/Program.csCombinedConditions

csharp
// <ConditionalCompilation>
static void ConfigureLogging()
{
#if DEBUG
    Console.WriteLine("Debug logging enabled — verbose output active.");
#else
    Console.WriteLine("Release logging — errors only.");
#endif
}
// </ConditionalCompilation>

// <CombinedConditions>
static void ShowPlatformInfo()
{
#if NET10_0_OR_GREATER
    Console.WriteLine("Running on .NET 10 or later.");
#elif NET8_0_OR_GREATER
    Console.WriteLine("Running on .NET 8 or 9.");
#else
    Console.WriteLine("Running on an older .NET version.");
#endif
}
// </CombinedConditions>

ConfigureLogging();
ShowPlatformInfo();

Use #define at the top of a file to define your own symbols. You can also define symbols for the entire project by using the DefineConstants property in your project file.

Warning suppression

Use #pragma warning disable to suppress specific compiler warnings, and #pragma warning restore to re-enable them. Always scope the suppression as narrowly as possible:

sample · csharpdocs/csharp/fundamentals/program-structure/snippets/preprocessor-directives/PragmaWarning.csPragmaWarning

csharp
namespace MyApp.Utilities;

class WarningDemo
{
    // <PragmaWarning>
    static void ProcessData()
    {
        try
        {
            // Some operation that might fail
            var data = File.ReadAllText("config.json");
            Console.WriteLine($"Config loaded: {data.Length} characters");
        }
#pragma warning disable CS0168 // Variable is declared but never used
        catch (FileNotFoundException ex)
#pragma warning restore CS0168
        {
            // Fall back to defaults — the exception details aren't needed here
            Console.WriteLine("Config file not found, using defaults.");
        }
    }
    // </PragmaWarning>
}