Team LiB
Previous Section Next Section

Using Code Attributes

This next section illustrates the concepts surrounding code attributes, including an overview of attributes and how they work, and finally a sample showing you how to create and consume custom code attributes.

Introduction to Code Attributes

The .NET Framework allows for many kinds of metadata to be stored with code. You can store resources embedded in assemblies, and assemblies have metadata that describes things such as product name, product version, and the list of required and referenced assemblies.

In addition to the standard metadata, there are code attributes that can be used to "decorate" code. These attributes provide extremely useful information about the code, and can even dictate how the code is executed by its environment.

Using Code Attributes

You might have seen code attributes in action but not noticed that you were using them. For example, when you create a method that is exposed to web service clients via WSDL, you decorate that method with the WebMethodAttribute attribute, as shown here:

[WebMethod()]
public string HelloWebServiceWorld()
{
    return "Hello World";
}

There are other attributes that control transaction behavior within COM+, attributes that control object serialization and XML serialization formats, and much more.

By convention, code attributes in the .NET Framework all have an Attribute postfix. Thankfully, C# recognizes this and enables you a shortcut of leaving off the postfix when declaring the attribute.

You saw earlier that data types actually inherit from System.Object, just like every other class in the .NET Framework. Similarly, code attributes are actually classes. When you decorate a class, member, or assembly with a code attribute, you are actually creating an instance of that class at runtime using either a default or a parameterized constructor.

The following code snippet shows a brief example of what a class decorated with your own custom attributes might look like:

[MyClassAttribute("Hello")]
public class MyClass
{
    private int my_Val;

    public MyClass() { }

    [MyPropertyAttribute(42)]
    public int MyValue { get { return my_Val; }
      set { my_Val = value; } }
}

As mentioned previously, you can leave off the Attribute postfix to make things more readable.

Creating Custom Attributes

It is important to recognize the difference between code attributes and comments. Code attributes provide additional details and information to runtime facilities. Code comments provide information to people reading the code at design-time.

WHEN TO ATTRIBUTE AND WHEN TO COMMENT

When deciding whether you should put information in an attribute or a comment, make sure that you put in attributes only information that must be available to the code at runtime. For example, people often use attributes to create object-relational database mappings by mapping members to stored procedure parameters or table columns. Developers use attributes for many other purposes as well. However, for simple comments that are useful only to people reading the code while it is not being executed, use commentsattributes add slight overhead to the application while running.


In the next section, you will see how to create your own custom attribute. For the purposes of demonstration, consider the following scenario:

You have developed a high-availability solution. A requirement of this solution is that administrators are notified the instant an exception is trapped within the application. In addition, the programmer who created the method in which the exception was thrown must be notified as well.

At design-time, it's fairly easy to tell who is responsible for writing a specific class or method. You can look at comments or source control reports or just ask around the office. At runtime, it's an entirely different situation. The only way for the code to know who developed a method at runtime is to decorate that method with an attribute.

As mentioned earlier, code attributes are nothing more than special classes. When you decorate code with an attribute, you are informing the Common Language Runtime to create an instance of that attribute class and make it available throughout the lifetime of that application.

The code in Listing 11.2 creates a custom code attribute class responsible for maintaining the name and email address of the author of a section of code.

Listing 11.2. A Sample Code Attribute Class
   using System;

   namespace AttributeDemo
   {
     [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
     public class CodeAuthorAttribute : System.Attribute
     {
       private string authorName = string.Empty;
       private string authorEmail = string.Empty;

       public CodeAuthorAttribute( string initAuthorName, string initAuthorEmail )
       {
         authorName = initAuthorName;
         authorEmail = initAuthorEmail;
       }

       public string AuthorName
       {
        get
        {
          return authorName;
        }
        set
        {
          authorName = value;
        }
       }

       public string AuthorEmail
       {
         get
         {
           return authorEmail;
         }
         set
         {
           authorEmail = value;
         }
       }
     }
   }

For the most part, it's a pretty simple class. It has a property that represents the name of the code author, and another property that represents the email address of the code author. The two lines that stand out are the line with the AttributeUsageAttribute declaration, and the line that indicates that the class inherits from the System.Attribute class.

Something interesting happens when you apply this code attribute to some other class , as shown in Listing 11.3.

Listing 11.3. A Class Decorated with a Custom Attribute
   using System;

   namespace AttributeDemo
   {
     public class ClassWithError
     {
       public ClassWithError()
       {
       }

       [CodeAuthor("Kevin Hoffman", "someemail@someaddress.com")]
       public void DoSomething()
       {
         try
         {
           // a little chicanery to get
           // a divide-by-zero error to compile
           int x = 12;
           int y = 4;
           int z = y-4;
           int aa = x/z;
         }
         catch (Exception e)
         {
           Console.WriteLine("Something went wrong!");
           Console.WriteLine(e.ToString());
         }
       }
     }
   }

Querying Custom Attributes

The code in the previous section showed you how to create your own custom attribute. That code showed you how to decorate a class with your own custom attribute. In this next section, you'll see how to use Reflection to find out what attributes are associated with a piece of code, and how to get at the values within those attributes.

In this next sample, you'll take some of the code from the previous sample and build on it. In the previous code, there was a simple exception handler to trap the division-by-zero error. In Listing 11.4, you'll add some code to display the name and email address of the author who wrote the code.

The first thing to do is create a method that will take a method as an argument, and return the method's author. Listing 11.4 shows a class with a static method that does just that.

Listing 11.4. A Utility Class to Find A Method's Author Using a Custom Code Attribute
using System;
using System.Reflection;
using System.Diagnostics;

namespace AttributeDemo
{
  /// <summary>
  /// Summary description for AttributeTool.
  /// </summary>
  public class AttributeTool
  {
    public AttributeTool()
    {

    }

    public static CodeAuthorAttribute GetMethodAuthor(MethodBase method)
    {
      object[] attributes =
        method.GetCustomAttributes( typeof(CodeAuthorAttribute), true );
      return (CodeAuthorAttribute)attributes[0];
    }
  }
}

The preceding code returns the first CodeAuthorAttribute found by calling the GetCustomAttributes method. This is a sample, so it doesn't handle the case in which no such attributes are found. In production code, you would want to handle such a situation.

With this method in place, you can modify the code in Listing 11.3 to obtain the author of the method and display that information to the console in the event of an unexpected exception. Listing 11.5 shows the modified class.

Listing 11.5. Code That Displays the Author of a Method After an Error
using System;
using System.Reflection;

namespace AttributeDemo
{
  public class ClassWithError
  {
    public ClassWithError()
    {
    }

    [CodeAuthor("Kevin Hoffman", "someemail@someaddress.com")]
    public void DoSomething()
    {
      try
      {
        // a little chicanery to get
        // a divide-by-zero error to compile
        int x = 12;
        int y = 4;
        int z = y-4;
        int aa = x/z;
      }
      catch
      {
        Console.WriteLine("Something went wrong!");
        CodeAuthorAttribute caa = AttributeTool.GetMethodAuthor
( MethodBase.GetCurrentMethod() );
        Console.WriteLine("The person you need to blame is {0} @ {1}",
          caa.AuthorName, caa.AuthorEmail);
      }
    }
  }
}

This code invokes the utility class that was created in Listing 11.4 to get an instance of the CodeAuthorAttribute. When the preceding code is executed, you should see output that looks like the following lines:

Something went wrong!
The person you need to blame is Kevin Hoffman @ someemail@someaddress.com

The code threw a division-by-zero exception, and then trapped it. The exception handler then used a utility class to retrieve information on the author of the method. Using the hypothetical scenario defined at the beginning of this section, you could expand the sample to send that programmer an email that contains a serialization of the exception thrown.

    Team LiB
    Previous Section Next Section