You are free to create your own custom attributes and use them at runtime as you see fit. Suppose, for example, that your development organization wants to keep track of bug fixes. You already keep a database of all your bugs, but you’d like to tie your bug reports to specific fixes in the code.
You might add comments to your code along the lines of:
// Bug 323 fixed by Jesse Liberty 1/1/2005.
This would make it easy to see in your source code, but there is no enforced connection to Bug 323 in the database. A custom attribute might be just what you need. You would replace your comment with something like this:
[BugFixAttribute(323,"Jesse Liberty","1/1/2005") Comment="Off by one error"]
You could then write a program to read through the metadata to find these bug-fix notations and update the database. The attribute would serve the purposes of a comment, but would also allow you to retrieve the information programmatically through tools you’d create.
Attributes,
like most things in C#, are embodied in classes. To create a custom
attribute, you derive your new custom attribute class from
System.Attribute
:
public class BugFixAttribute : System.Attribute
You need to tell the compiler with which kinds of elements this attribute can be used (the attribute target). You specify this with (what else?) an attribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)]
AttributeUsage
is an attribute applied to
attributes: a meta-attribute. It provides, if you will,
meta-metadata—that is, data about the metadata. For the
AttributeUsage
attribute constructor, you pass two
arguments. The first argument is a set of flags that indicate the
target—in this case, the class and its constructor, fields,
methods, and properties. The second argument is a flag that indicates
whether a given element might receive more than one such attribute.
In this example, AllowMultiple
is set to
true
, indicating that class members can have more
than one BugFixAttribute
assigned.
The new custom
attribute
in this example is named BugFixAttribute
. The
convention is to append the word Attribute
to your
attribute name. The compiler supports this by allowing you to call
the attribute with the shorter version of the name. Thus, you can
write:
[BugFix(123, "Jesse Liberty", "01/01/05", Comment="Off by one")]
The compiler will first look for an attribute named
BugFix
and, if it does not find that, will then
look for BugFixAttribute
.
Every
attribute
must have at least one constructor. Attributes take two types of
parameters,
positional
and named
. In
the BugFix
example, the programmer’s name
and the date are positional parameters, and
comment
is a named parameter. Positional
parameters are passed in through the constructor and must be passed
in the order declared in the constructor:
public BugFixAttribute(int bugID, string programmer, string date) { this.bugID = bugID; this.programmer = programmer; this.date = date; }
Named parameters are implemented as properties:
public string Comment { get { return comment; } set { comment = value; } }
It is common to create read-only properties for the positional parameters:
public int BugID { get { return bugID; } }
Once you have defined an attribute,
you can put it to work by placing it immediately before its target.
To test the BugFixAttribute
of the preceding
example, the following program creates a simple class named
MyMath
and gives it two functions. You’ll
assign BugFixAttributes
to the class to record its
code-maintenance history:
[BugFixAttribute(121,"Jesse Liberty","01/03/05")] [BugFixAttribute(107,"Jesse Liberty","01/04/05", Comment="Fixed off by one errors")] public class MyMath
These attributes will be stored with the metadata. Example 18-1 shows the complete program.
Example 18-1. Working with custom attributes
namespace Programming_CSharp
{
using System;
using System.Reflection;
// create custom attribute to be assigned to class members
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
public class BugFixAttribute : System.Attribute
{
// attribute constructor for
// positional parameters
public BugFixAttribute
(int bugID,
string programmer,
string date)
{
this.bugID = bugID;
this.programmer = programmer;
this.date = date;
}
// accessor
public int BugID
{
get
{
return bugID;
}
}
// property for named parameter
public string Comment
{
get
{
return comment;
}
set
{
comment = value;
}
}
// accessor
public string Date
{
get
{
return date;
}
}
// accessor
public string Programmer
{
get
{
return programmer;
}
}
// private member data
private int bugID;
private string comment;
private string date;
private string programmer;
}
// ********* assign the attributes to the class ********
[BugFixAttribute(121,"Jesse Liberty","01/03/05")]
[BugFixAttribute(107,"Jesse Liberty","01/04/05",
Comment="Fixed off by one errors")]
public class MyMath
{
public double DoFunc1(double param1)
{
return param1 + DoFunc2(param1);
}
public double DoFunc2(double param1)
{
return param1 / 3;
}
}
public class Tester
{
public static void Main( )
{
MyMath mm = new MyMath( );
Console.WriteLine("Calling DoFunc(7). Result: {0}",
mm.DoFunc1(7));
}
}
}
Output:
Calling DoFunc(7). Result: 9.3333333333333339
As you can see, the attributes had absolutely no impact on the output. In fact, for the moment, you have only my word that the attributes exist at all. A quick look at the metadata using ILDasm does reveal that the attributes are in place, however, as shown in Figure 18-1. We’ll see how to get at this metadata and use it in your program in the next section.
3.144.172.38