Since I've been a programmer and learned how to use the things, I never liked Complier Directives. Ever since I first encountered them, I thought they were ugly and there was nothing Object Oriented about them. Before I even knew what Declarative Programming was, I was yearning for it. I'll be the first to admit that Compiler Directives were a necessary evil in the past, emphasis on the evil part.
Anyway, in comes .NET and there are these really cool things called Attributes that fix so much of what was broken in the programming world. Creating Custom Attributes is a discussion unto itself (one I probably need to write about soon) but let me run through why I like them so much.
In the old days of ugly compiler directives you would mark code with Conditional statements and then define build constants. When the code was compiled, the preprocessor directives indicated what would be executed and what wouldn't be. Syntactically, they were clunky often looking like this:
| #If Not Something Then #End If |
| public class ConnectionManager { private string DebugConnectString = "blah blah blah; testdatabase; blah blah "; private string ReleaseConnectString = "blah blah blah; Releasedatabase; blah blah "; private SqlConnection cn; [Conditional("DEBUG")] private void SetConnectionDebug() { cn.ConnectionString = DebugConnectString; } private void SetConnectionRelease() { cn.ConnectionString = ReleaseConnectString; } public ConnectionManager() { //Create a new Instance of cn, but without //an initialized ConnectionString cn = new SqlConnection(); //If built in release this is the only one that will be set //(The ConnectString pointing to Production even though //we are calling SetConnectionDebug right afterware - //it's because SetConnectionDebug will never execute SetConnectionRelease(); //This one will reset the connection string to the Debug //Database if that attribute is defined SetConnectionDebug(); } } |
#if DEBUG cn.ConnectionString = DebugConnectString; #else cn.ConnectionString = ReleaseConnectString #endif |



| <?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <!-- User application and configured property settings Vo here.--> <!-- Example: <add key="settingName" value="settingValue"/> --> <add key="sqlConnection1.ConnectionString" value="" /> <add key="button1.AccessibleDescription" value="(None)" /> <add key="button1.AccessibleName" value="(None)" /> <add key="button1.AllowDrop" value="False" /> <add key="button1.CausesValidation" value="True" /> <add key="button1.Enabled" value="True" /> <add key="button1.ImageIndex" value="-1" /> <add key="button1.TabIndex" value="0" /> <add key="button1.TabStop" value="True" /> appSettings> configuration> |
| //I left some of them out for brevity this.button1.AccessibleName = ((string)(configurationAppSettings.GetValue("button1.AccessibleName", typeof(string)))); this.button1.AllowDrop = ((bool)(configurationAppSettings.GetValue("button1.AllowDrop", typeof(bool)))); this.button1.CausesValidation = ((bool)(configurationAppSettings.GetValue("button1.CausesValidation", typeof(bool)))); this.button1.Enabled = ((bool)(configurationAppSettings.GetValue("button1.Enabled", typeof(bool)))); this.button1.ImageIndex = ((int)(configurationAppSettings.GetValue("button1.ImageIndex", typeof(int)))); this.button1.Location = new System.Drawing.Point(104, 48); this.button1.Name = "button1"; this.button1.TabIndex = ((int)(configurationAppSettings.GetValue("button1.TabIndex", typeof(int)))); this.button1.TabStop = ((bool)(configurationAppSettings.GetValue("button1.TabStop", typeof(bool)))); this.button1.Text = "button1"; this.button1.Click += new System.EventHandler(this.button1_Click); |
| [Conditional("DEBUG"), Conditional("DeployLocation")] private void SetConnectionDebug() { cn.ConnectionString = DebugConnectString; } In this instance, both must be true for this to execute which once again is much cleaner than the nested #IF's. If you don't think that's very readable, you can use this syntax as well: [Conditional("DeployLocation")] [Conditional("DEBUG")] private void SetConnectionDebug() { cn.ConnectionString = DebugConnectString; } They will both compile and behave the same. Now, let's whip up our own attribute. One of the cooler examples I saw is a BugFix attribute. Whenever a programmer fixes a bug, it'd be nice to have a record of it. Sure tools like Source Safe can tell you that things changed, but you have to dig through them and compare them manually. It's not often the fastest thing in the world. So let's say your department implemented a MustUseAttributes rule and each time you made a bug fix, you noted it with an attribute. Here's a class I got from Jesse Liberty's superb book |
| namespace MyCompany { /// <summary> /// This is an example from Jesse Liberty used in /// his Programming C# Book - http://www.oreilly.com/catalog/progcsharp/chapter/ch18.html /// I thought it was a particularly useful example and if you haven't /// purchased his book yet, I highly recommend it. /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] public class BugFixAttribute: System.Attribute { public BugFixAttribute(int bugID, string programmer, string date) { this.bugID = bugID; this.programmer = programmer; this.date = date; } } public string Comment { get{return comment;} set{comment = value;} } public int BugID {get{return bugID;} } Now, to use it in your code [Conditional("DeployLocation")] [Conditional("DEBUG")] [BugFixAttribute(121,"Bill Ryan Thanks to Jesse Liberty","12/10/03")] [BugFixAttribute(107,"Bill Ryan Thansk to Jesse Liberty","12/11/03", Comment="Moved the Database and changed the ConnectionString")] private void SetConnectionDebug() { cn.ConnectionString = DebugConnectString; } |