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:
#!/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:
#:package Serilog@3.1.1Other #: directives let you reference projects, set MSBuild properties, or change the SDK:
#:project ../SharedLibrary/SharedLibrary.csproj
#:property PublishAot=false
#:sdk Microsoft.NET.Sdk.WebFor 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
// <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
// <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
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>
}