Sunday, October 31, 2010

.NET ConditionalAttribute

The preface to this blog post is found here Windows Forms .NET handling unhandled exceptions. The short story being that I have a smart client / winforms application running on the clients machine in release mode. All unhandled exceptions caught are reported to a sharepoint list. However in debug mode on the developer machine we do not want to handle unhandled exceptions since this can be useful when debugging in Visual Studio.

Hence we need to differentiate between debug and release mode.

This have traditionally been done using the compiler directives as here below
static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
#if DEBUG
            // To be able to debug properly we do not want to steal the exceptions from VS IDE
            safeMain();
#else
            // In release mode we want to be exception safe
                        try
            {
                    safeMain();
            }
            catch (Exception exception)
            {
                    handleUnhandledExceptions(exception);
            }
#endif
        }

        /// <summary>
        /// safeMain is a try catch all for any possible unhandled exceptions
        /// </summary>
        static void safeMain()
        {
#if !DEBUG 
            // In release mode we want to be exception safe
            // CLR unhandled exceptions handler
            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
            // Windows forms exceptions handler
            Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);
#endif
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }
This however is both messy and can lead to mistakes as I will show later.

The .NET framework have a useful attribute class that makes arranging this code in a better way very easy. The ConditionalAttribute.

Lets look as some code
using System;
using System.Linq;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;

namespace ConditionalAttrb
{
 public partial class Form1 : Form
 {
  public Form1()
  {
   InitializeComponent();
  }

  private void button1_Click(object sender, EventArgs e)
  {
   Log();
  }

  [Conditional("DEBUG")]
  private void Log()
  {
   label1.Text = "Inside Log()";
  }

  private void Form1_Load(object sender, EventArgs e)
  {
   if (IsAssemblyDebugBuild(this.GetType().Assembly))
   {
    this.Text = "Conditional Attribute (Debug)";
   }
   else
   {
    this.Text = "Conditional Attribute (Release)";
   }
  }

  private bool IsAssemblyDebugBuild(Assembly assembly)
  {
   return assembly.GetCustomAttributes(false).Any(x => (x as DebuggableAttribute) != null ? (x as DebuggableAttribute).IsJITTrackingEnabled : false);
  }
 }
}
The Form1_Load event handler method is only here used to set the correct title on the form so it is easy to see if you are in debug or release mode. It uses the IsAssemblyDebugBuild helper method for this.

The 2 only methods that are really relevant to the ConditionalAttribute class is the button1_Click event handler method and the Log() method.

You can see from the screenshots here below what happens when you run the application in debug / release mode and click the button.




This is more or less to be expected after all we only want the logging to happen when in debug mode.

So what is going on behind the scenes, lets have a look at the MSIL code for the Log() in debug and then release mode.

Debug:
.method private hidebysig instance void  Log() cil managed
{
  .custom instance void [mscorlib]System.Diagnostics.ConditionalAttribute::.ctor(string) = ( 01 00 05 44 45 42 55 47 00 00 )                   // ...DEBUG..
  // Code size       19 (0x13)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldfld      class [System.Windows.Forms]System.Windows.Forms.Label ConditionalAttrb.Form1::label1
  IL_0007:  ldstr      "Inside Log()"
  IL_000c:  callvirt   instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
  IL_0011:  nop
  IL_0012:  ret
} // end of method Form1::Log

Release:
.method private hidebysig instance void  Log() cil managed
{
  .custom instance void [mscorlib]System.Diagnostics.ConditionalAttribute::.ctor(string) = ( 01 00 05 44 45 42 55 47 00 00 )                   // ...DEBUG..
  // Code size       17 (0x11)
  .maxstack  8
  IL_0000:  ldarg.0
  IL_0001:  ldfld      class [System.Windows.Forms]System.Windows.Forms.Label ConditionalAttrb.Form1::label1
  IL_0006:  ldstr      "Inside Log()"
  IL_000b:  callvirt   instance void [System.Windows.Forms]System.Windows.Forms.Control::set_Text(string)
  IL_0010:  ret
} // end of method Form1::Log
If we study the MSIL code closely we see that the code is the same apart from 2 nop.

From this we can conclude that the method Log() is the same in debug and release mode. Now let us have a look at the calling button1_Click() method.

Debug:
.method private hidebysig instance void  button1_Click(object sender,
                                                       class [mscorlib]System.EventArgs e) cil managed
{
  // Code size       9 (0x9)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  call       instance void ConditionalAttrb.Form1::Log()
  IL_0007:  nop
  IL_0008:  ret
} // end of method Form1::button1_Click

Release:
.method private hidebysig instance void  button1_Click(object sender,
                                                       class [mscorlib]System.EventArgs e) cil managed
{
  // Code size       1 (0x1)
  .maxstack  8
  IL_0000:  ret
} // end of method Form1::button1_Click
If we look at this MSIL code we can see that it is the calling function that is modified not the Log() method. Since the code is JIT'ed the extra Log() method does not incur a performance overhead in release mode since the JIT compiler looks ahead and only JITs methods that will be called.

Ok so what is the benefit apart from that my code looks better?

Assuming that you are writing the following code
using System;
using System.Linq;
using System.Windows.Forms;
using System.Diagnostics;
using System.Reflection;

namespace ConditionalAttrb
{
 public partial class Form1 : Form
 {
  public Form1()
  {
   InitializeComponent();
  }

  private void button1_Click(object sender, EventArgs e)
  {
   string message = null;
#if DEBUG
   log = "Logging";
#endif 
   Log(message);
  }

  private void Log(string message)
  {
   if (message == null) throw new ArgumentNullException("message");
   label1.Text = message;
  }

  private void Form1_Load(object sender, EventArgs e)
  {
   if (IsAssemblyDebugBuild(this.GetType().Assembly))
   {
    this.Text = "Conditional Attribute (Debug)";
   }
   else
   {
    this.Text = "Conditional Attribute (Release)";
   }
  }

  private bool IsAssemblyDebugBuild(Assembly assembly)
  {
   return assembly.GetCustomAttributes(false).Any(x => (x as DebuggableAttribute) != null ? (x as DebuggableAttribute).IsJITTrackingEnabled : false);
  }
 }
}

This is a contrived example however it is not unusual for conditional code to become rather complex over the time of a project. And this code is actually introducing a fatal flaw in release mode which is not present in debug mode.



Ideally the code base should be the same in debug and release mode, and conditional compiler directives is in IMHO a bad idea when we have the ConditionalAttribute.
Post a Comment