Attributes

An often-overlooked feature of C# is the attribute. You might have seen them in code you’ve looked at: they appear as what look like variable names or method calls enclosed in square brackets that appear before method or class definitions (or sometimes in other places). So what are they?

Put simply, an attribute is a way of storing information about the code within the code itself; meta-information if you like. There are several pre-defined attributes you can use, but in this post we’ll look at how you can define your own attributes.

As an example, suppose you want to store creation and modification dates for some of the methods in your code. Typically, a given method has one creation date, but could have any number of modification dates. We can store each of these dates as an attribute attached to each method.

A class used to create attribute objects must inherit the Attribute class (in the System namespace). If the class has a constructor that requires parameters, these parameters must all be primitive types (things like int, float, double, string and so on). This is due to technical aspects concerning the way attributes are stored in the code, so we won’t go into that here; just remember that you can’t pass anything other than primitive data types in an attribute’s constructor.

Since we want to store dates, this means that we can’t use a DateTime structure as a parameter directly; we’ll need to pass the day, month and year as separate int parameters. With that in mind, here’s the definition of the Created attribute class which we’ll use to store the date a method was created:

  public class Created : Attribute
  {
    public Created(int year, int month, int day)
    {
      Date = new DateTime(year, month, day);
    }

    public DateTime Date { get; set; }
  }

You’ll see that Created inherits the Attribute class, and that it has one constructor used to initialize the Date object. (There are a number of other features that can be specified when defining an attribute class, which we’ll get to in a minute. This is the simplest attribute definition, in which we accept the default values for all these features.)

With this definition, we can add a Created attribute to a method.

    [Created(2012, 6, 1)]
    public void Method_1()
    {
      // ........
    }

We put the attribute in square brackets immediately before the method to which it’s attached. The attribute code is just a call to the Created constructor, and the attribute is a Created object.

For the Modified attribute, we need the same data (a DateTime) stored, but we want to allow more than one Modified attribute for each method. The default behaviour of an attribute is to allow only a single instance of that attribute per method. However, we can override this behaviour by giving the attribute class itself an attribute. So we define our Modified attribute as follows:

  [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple=true)]
  public class Modified : Attribute
  {
    public Modified(int year, int month, int day)
    {
      Date = new DateTime(year, month, day);
    }

    public DateTime Date { get; set; }
  }

The class definition here is the same as for Created; the difference is the AttributeUsage line at the start. AttributeUsage is a System pre-defined attribute which specifies how the following attribute class is allowed to be used. This code is just another call to the AttributeUsage constructor, this time with two parameters. The first parameter specifies what C# language elements the attribute can be used with. The default is AttributeTargets.All, which applied to our Created attribute above. Here, we’ve specified that Modified can be used methods and classes, though there are several other language elements that could be specified as well.

The second parameter gives a value for AllowMultiple, which defaults to false. Since we want to allow multiple Modified attributes, we set this to true. Note that there’s no way of setting just the AllowMultiple parameter without also specifying the AttributeTargets, since AllowMultiple is always the second parameter in the constructor.

With this definition, we can now add some Modified attributes to our methods. Here is the revised code, this time with two methods:

    [Created(2012, 6, 1)]
    [Modified(2012, 6, 12), Modified(2012, 8, 27)]
    public void Method_1()
    {
      // ........
    }

    [Created(2011, 11, 14)]
    [Modified(2011, 12, 12), Modified(2012, 2, 29), Modified(2012, 4, 30)]
    public void Method_2()
    {
      // .........
    }

All this is fine, but clearly attributes aren’t much use if all we can do is define them. We need a way of accessing their values later on. This is done using C#’s reflection techniques.

Reflection allows us to access properties of the code that is running at the time. We can extract information on pretty well all levels of the code, including classes and individual methods within classes. Here’s the code for accessing and printing the attributes we defined above:

    static void Main(string[] args)
    {
      MethodInfo[] methods = typeof(Program).GetMethods();
      foreach (MethodInfo method in methods)
      {
        WriteHistory(method);
      }
    }

    public static void WriteHistory(MethodInfo method)
    {
      Console.WriteLine("History for {0}", method.Name);
      Attribute[] attrs = Attribute.GetCustomAttributes(method);
      foreach (Attribute attr in attrs)
      {
        PropertyInfo dateInfo = attr.GetType().GetProperty("Date");
        if (dateInfo != null)
        {
          Console.WriteLine("    {0}: {1}", attr.GetType(),
            ((DateTime)dateInfo.GetValue(attr)).ToString("MMM dd yyyy"));
        }
      }
    }

These two static methods are defined in the same class (called Program) as the other methods above, but we could equally well have defined them elsewhere; we’d just need to modify the code a bit to access the information.

The Main() method finds the type of the enclosing Program class and calls GetMethods() to return an array of MethodInfo objects for each method in the class. This array includes entries for the methods we’ve defined here, but it also includes methods inherited from the base class (‘object’ in this case).

We then loop through this array and pass each MethodInfo object to the WriteHistory() method.

The MethodInfo class, as its name implies, contains information on the method which it represents. In WriteHistory(), we first write out the method’s name, then we extract an array of Attribute objects for that method.

In the loop over the Attribute objects, we want to print out the value of the Date property for each attribute (whether it’s Created or Modified). In this special case, since both attributes contain a Date property, we can use  the Type class’s GetProperty() method to extract the Date property, and store the result in a PropertyInfo object.

If it finds the desired property (if the result isn’t null), we can then print out its value, where we’ve cast this to a DateTime and given the ToString() method a formatting string to make the date appears neat.

The output of this program is:

History for Method_1
    AttributeTest02.Modified: Aug 27 2012
    AttributeTest02.Modified: Jun 12 2012
    AttributeTest02.Created: Jun 01 2012
History for Method_2
    AttributeTest02.Modified: Apr 30 2012
    AttributeTest02.Created: Nov 14 2011
    AttributeTest02.Modified: Dec 12 2011
    AttributeTest02.Modified: Feb 29 2012
History for WriteHistory
History for ToString
History for Equals
History for GetHashCode
History for GetType

You can see that the attributes don’t always appear in the same order in which they were defined, so you may need to sort the output if the order is important. For example, here we’d usually like the Created attribute to appear before the Modified ones, and the Modified ones to be in chronological order. These are details we can deal with later.

Note also that the GetMethods() method returns all the methods for the Program class (apart from Main()), some of which are inherited.

Advertisements
Post a comment or leave a trackback: Trackback URL.

Trackbacks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: