Friday, June 12, 2026

C# Preprocessor directives

In this post we will see what are C# preprocessor directives, and what is their purpose.

C# preprocessor directives are a salient feature that allows you to conditionally include, exclude, or organize code before compilation.

Preprocessor directives are instructions processed by the C# compiler before actual compilation. They all begin with the # character. Their key characteristic is that they have no effect at runtime; they simply determine which code gets compiled and which does not.

Main points of Preprocessor directives:

  • They provide instruction code to compiler before compilation process.
  • Based on the instruction/directive, a piece of code can be excluded or included for compilation
  • All directives begin with the # character.
  • They have no effect at runtime; they simply determine which code gets compiled and which does not.

There are different C# preprocessor directives which are used for different purposes. Some of them are tabulated below:

C# Preprocessor Directives List
DirectiveMeaning
#define Defines a compilation symbol that can be used in conditional compilation.
#undef Undefines a previously defined symbol.
#if Compiles code only if a specified symbol or expression evaluates to true.
#elif Specifies an alternative condition after an #if or another #elif.
#else Specifies code to compile when preceding #if or #elif conditions are false.
#endif Marks the end of an #if, #elif, or #else block.
#warning Generates a custom compiler warning message.
#error Generates a custom compiler error and stops compilation.
#line Changes the compiler's line-number and file-name information for error reporting and debugging.
#region Defines a collapsible code region in IDEs such as Visual Studio.
#endregion Marks the end of a #region block.
#pragma Controls compiler behavior, such as enabling or disabling specific warnings.
#nullable Controls nullable reference type annotations and warnings.

#define and #undef — Defining symbols

These preprocessor directives define or remove a symbol. These symbols are used anywhere in the code to check conditions. A directives symbol does not have a value; it simply either exists or it doesn't.
EXAMPLE 1
#define DEBUG_MODE
#define PREMIUM_USER
class Program
{
    static void Main()
    {
#if DEBUG_MODE
        Console.WriteLine("Debug: Application starting...");
#endif

#undef PREMIUM_USER   // symbol removed

#if PREMIUM_USER
        Console.WriteLine("Premium features"); // it will not execute
#endif
    }
}
Preprocessor directives (#define and #undef) are always written at the top of the file, before any using statements. In Real-world,
DIRECTIVE  USAGE
#if DEBUG  debug-only logging
#if WINDOWS  platform-specific code
#pragma warning disable  legacy code warnings
#nullable enable  to write modern null-safe code.

C# Type Determination and Type Conversions using is, as and cast operators

Since C# is a type-safe language, an expression of a particular type can only be assigned to a compatible type; this often necessitates type conversion. The C# language offers various methods and operators to facilitate this conversion or checking the datatype of the expression. C# provides various operators (e.g. as keyword or cast operator) to convert an expression from one type into another type; it also provide is operator to check if an expression is of some specific type or not. 

The knowledge of these operators is essential for any C# programmer, as they are routinely needed by the developers. In this post we will see the differences between as, is and cast operators.

IS OPERATOR

First, we consider about is operator. It is a binary operator which takes expression as left operand and a datatype as right operand. It checks if the expression is of the datatype given in the right operand. If true, then it returns True else returns False. Now, consider the following example:
class Program
{
    static void Main(string[] args)
    {
        int number = 42;
        Console.WriteLine(number is int); // True
        Console.WriteLine(number is double); // False
        Console.WriteLine(number is Customer); // False
        Customer customer = new Customer { Name = "John Doe", Age = 30 };
        RegularCustomer regularCustomer = new RegularCustomer { Name = "Jane Doe", Age = 25, Discount = 0.1m };
        Console.WriteLine(customer is int); // False
        Console.WriteLine(customer is Customer); // True
        Console.WriteLine(customer is RegularCustomer); // False
        Console.WriteLine(regularCustomer is Customer); // True
    }
}
class Customer
{
    public string Name { get; set; }
    public int Age { get; set; }
}

class RegularCustomer : Customer
{
    public decimal Discount { get; set; }
}

The is operator can be used to check the datatype of expression which can be be value type or reference type. In above example, number expression is checked against value type and reference type both. Similarly, reference type customer variable is checked againt value type and reference type both. The as operator is used only for reference type as we will see later.

The is operator returns a Boolean value. It means the result of the is operation can be either True or False. Based on the result, developer can decide whether the conversion will be successful or not. Thereafter inside the if block, the cast operation is done for success.

AS OPERATOR

Second, we consider about as operator. It is a binary operator which takes expression as left operand and a datatype as right operand, very much similar to is operator. It checks if the expression is of the datatype given in the right operand. If true, then expression is evaluated/converted as per the datatype given in the right operand. If false, then it returns null.

The as operator cannot be used to check expression against a value type. The right side operand must be a reference type.

Now, consider the following example:

class Program
{
    static void Main(string[] args)
    {
        int number = 42;
        object obj = new object(); 
        obj = number as int;  // Compile time error as int is not reference type
        obj = number as double;  // Compile time error as double is value type
        obj = number as Customer;  // Compile time error, number cannot be converted to Customer
        obj = number as object;  // NO Compile time error, number can be boxed inside object
    }
}
class Customer
{
    public string Name { get; set; }
    public int Age { get; set; }
}
From the above code it is obvious that compiler throws error when value type (int and double) as used againt number expression. The compiler error mesage is as follows:
  • The as operator must be used with a reference type or nullable type ('int' is a non-nullable value type)
For the "number as Customer", the compiler error mesage is as follows:
  • Cannot convert type 'int' to 'Customer' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion
But for the "number as Object", the compiler does not throw any error mesage. The fact is number which is int type is boxed as object type.

Now we modify the above code to allow nullable type:
class Program
{
    static void Main(string[] args)
    {
        int number = 42;
        object obj = new object();
        obj = number as int?;
        Console.WriteLine(obj); // 42
        Console.WriteLine(obj.GetType()); // System.Int32
        obj = number as double?;
        Console.WriteLine(obj); // null
        //Console.WriteLine(obj.GetType()); null reference
        obj = number as object;
        Console.WriteLine(obj); // 42
        Console.WriteLine(obj.GetType()); // System.Int32
    }
}
class Customer
{
    public string Name { get; set; }
    public int Age { get; set; }
}
Note that when right operand is nullable value type, the compiler does not throw any error mesage. In fact, the expression: number as int? returns value 42 with System.Int32 datatype.

You can read the similar post using the following link: C# Differences among is, as and cast operators

Hot Topics