If you are working on C#, you probably know about methods like Debug.WriteLine. The important part of their usage is that they only do stuff if you are running an application compiled in Debug mode. If you are running in release mode, it works like those calls in your code don't even exist. When you compile your code with Debug.WriteLine() method call in debug mode and look at the MSIL, you can see the method call to Debug.WriteLine() and when you compile the same code in the Release mode and look at the MSIL, you cannot find the method call in the MSIL.Have you ever wondered how these methods are implemented? Have you ever wanted to create a method like this?
This can be achieved using pre-processor directives in C#, but wait, when you use pre-processor directives, you have to use the pre-processor directives before the method declaration and of course, before the method usage otherwise the compiler will give error. But here, we are not using any pre-processor directives before the method call. How the magic is done? This is simple, if you look at the syntax of the method Debug.WriteLine(), you can find one attribute [Conditional("DEBUG")] before the method.
What is Conditional Attribute?
Conditional Attribute tag on the method signature means that the method call only exists if the specified pre-processor directive is defined. In this case, the method call only exists if the symbol DEBUG is defined.
For example, take a look at the following code:
class Program
{
static void Main(string[] args)
{
DebugInfo();
}
[Conditional("DEBUG")]
public static void DebugInfo()
{
Console.WriteLine("This is debug information");
}
}
When this code is compiled in debug mode, if you take a look at the MSIL for the Main() method inside the assembly, you will see the following:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
L_0000: nop
L_0001: call void ConditionalAttributeTest.Program::DebugInfo()
L_0006: nop
L_0007: ret
}
And if you compile the same code in in Release mode and look at the MSIL for the Main() method, you can see the following:
.method private hidebysig static void Main(string[] args) cil managed
{
.entrypoint
.maxstack 8
L_0000: ret
}
Here, we cannot see the call to the method DebugInfo(). But we can see the Method DebugInfo present in the MSIL. The Conditional attribute instructs the compiler to remove the method call to this method.
Where is this usefull?
For example, you wanted to gather some performance data when the symbol TIMERS is defined. You can have some code like the following:
public static class StopWatches
{
private static Dictionary<string, Stopwatch> _stopwatches
= new Dictionary<string, Stopwatch>();
[Conditional("CHECKPERFORMANCE")]
public static void StartStopwatch(string key)
{
if (_stopwatches.ContainsKey(key))
{ return; }
_stopwatches.Add(key, Stopwatch.StartNew());
}
[Conditional("CHECKPERFORMANCE")]
public static void StopStopwatch(string key)
{
if (!_stopwatches.ContainsKey(key))
{ return; }
var watch = _stopwatches[key];
watch.Stop();
_stopwatches.Remove(key);
Console.WriteLine(String.Format("Timer: {0}, {1}ms", key,
watch.Elapsed.TotalMilliseconds));
}
}
class Program
{
static void Main(string[] args)
{
StopWatches.StartStopwatch("forloop");
int total = 0;
for (int i = 0; i < 10000; i++)
{ total += i; }
Console.WriteLine(total);
StopWatches.StopStopwatch("forloop");
Console.Read();
}
}
When you run the above code with #define CHECKPERFORMANCE, you will get the following output.
49995000
Timer: forloop, 0.9802ms
When you run the above code without defining the pre-processor directive CHECKPERFORMANCE, you will see the following output.
6b3c6f26-e72a-494b-b30a-cc0aaffcd85f|1|5.0
.Net Tips, Technical, c#
c# attributes, Conditional Attribute, ConditionalAttribute