Back to Blog
C#

C# Conditional Attribute: How It Works and When to Use It

c# conditional attribute: Learn how the C# Conditional attribute controls method call inclusion at compile time, with practical examples and limitations.

ConditionalAttributeDebugPreprocessorCompilationMethod CallsDebugging
A visual representation of the C# Conditional attribute filtering method calls at compile time.

c# conditional attribute requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The Conditional attribute in C# (from System.Diagnostics) lets you mark a method so that calls to it are omitted unless a specified preprocessor symbol is defined. It is a compile-time mechanism, not a runtime check. This article explains how it works, where it fits, and what limitations you need to respect.

What the Conditional Attribute Does

The Conditional attribute is applied to a method declaration. When the compiler sees a call to that method, it checks whether the symbol passed to the attribute is defined at the point of the call. If the symbol is not defined, the call expression is removed entirely from the emitted IL. The method definition remains in the assembly, but no call sites are generated.

This is different from #if directives, which conditionally compile the method body itself. With Conditional, the method always exists, but calls are stripped based on the caller's compilation context.

Using Conditional with a Debug Symbol

The most common use is to gate logging or assertion helpers behind the DEBUG symbol. For example:

using System.Diagnostics; public class Logger { [Conditional("DEBUG")] public static void Log(string message) { Console.WriteLine(message); } }

When you build in Debug configuration, DEBUG is defined, so calls like Logger.Log("start") are kept. In Release builds, the same call is removed from the IL, and the method is never invoked.

The symbol does not have to be DEBUG. You can define any symbol, such as TRACE or a custom one, and pass it to the attribute.

How the Compiler Removes Calls

The removal happens at compile time, not runtime. The compiler replaces the call with a no-op. If the method returns void, the call statement simply disappears. If the method returns a value, the call expression is replaced with the default value of the return type. However, the attribute is only valid on methods that return void; attempting to apply it to a method with a return value causes a compile error.

Because the removal is compile-time, there is no runtime overhead from a conditional check. The IL is smaller, and the method is not called. This can be useful for debug-only instrumentation that should have zero cost in production.

Comparing Conditional with #if Directives

#if and Conditional both conditionally include code, but they operate at different levels. #if controls what the compiler sees in the source file. The code inside an #if block is not parsed if the symbol is undefined. Conditional keeps the method definition but removes calls.

Aspect#ifConditional
ScopeSource code blockMethod call sites
Method definitionExcluded if symbol undefinedAlways compiled
Call sitesNot compiled if symbol undefinedRemoved from IL if symbol undefined
Runtime costNone (code absent)None (calls removed)
DebuggingCode not present in assemblyMethod exists, but calls missing

#if is useful when you want to exclude a method entirely from the assembly. Conditional is useful when you want the method to exist (for reflection, for example) but not be called from normal code.

Restrictions and Edge Cases

The Conditional attribute has several constraints:

  • The method must return void. You cannot apply it to a method that returns a value.
  • The method cannot be an override of a virtual method. Applying the attribute to an override is not allowed.
  • The method cannot be part of an interface implementation. If you mark an interface method, the implementing method must also be marked, but the attribute is not allowed on interface members.
  • The attribute can be applied to a method in a class, struct, or module.
  • The symbol is evaluated at the call site, not at the method definition. This means a call from an assembly that does not define the symbol will be removed, even if the method's own assembly defines it.

This last point is important: the decision is based on the caller's compilation symbols, not the callee's. So if you build a library with DEBUG defined and call the method from an application built without DEBUG, the call is removed.

Production Considerations and Maintainability

Using Conditional can make code harder to reason about. Because calls disappear in certain builds, you might not see them in stack traces or debugging sessions. This is often acceptable for logging, but it can hide side effects if the method does more than just log.

Avoid using Conditional for methods that have observable behavior beyond diagnostics. If a method performs a necessary operation, removing its call could break the application. The attribute is best reserved for debug-only assertions, tracing, and similar instrumentation.

Also, be aware that the method itself is still compiled. If you call it via reflection, it will execute regardless of the symbol. This can be a useful escape hatch, but it also means the method's code is present in the assembly, increasing its size slightly.

When you use Conditional, document the symbol it depends on. A developer reading the code may not know why a call is missing. A clear comment or a naming convention like LogDebug can help.

Final Technical Note: Interaction with Other Attributes

The Conditional attribute can be combined with other attributes, but the removal happens after attribute processing. For example, if you apply [Obsolete] and [Conditional] to the same method, the obsolete warning is emitted at compile time, but the call is still removed if the symbol is undefined. This can be confusing, so use such combinations sparingly.

Also, the attribute is inherited from System.Attribute and is not inherited by derived methods. If you mark a base class method, derived classes do not automatically get the same behavior unless they also apply the attribute.

c# conditional attribute: Usage and Behavior | RYUSLOG DEV