This article is also available as a TechRepublic download, which includes a sample Visual Studio project file with examples of custom attributes.

Attributes
are special classes that can be applied to classes, properties, and methods at
design time. Attributes provide a way to describe certain aspects of an element
or determine the behavior of other classes acting upon the element. Those
descriptions and behaviors can then be accessed and examined at runtime. You
can think of attributes as a way of adding special modifiers to your class
members.

For
example, if you have written Web services, you are no doubt aware that the WebMethod attribute must be applied to methods for
them to be exposed through the service. This is a perfect example to show the
usage of attributes because the WebMethod attribute
is used to extend the programming model. There is no built-in way in C# of
signifying that a method should be exposed through the Web service (as there
is, for example, of signifying that a method should be private), so the WebMethod attribute was written to satisfy this need.

Developing custom attributes

The
process of creating a custom attribute is very simple. There are just a few
things you must take into account before creating the attribute:

  • What is the
    purpose of the attribute?

    Attributes can be used in any number of ways. You need to define what
    exactly the attribute is meant to accomplish and make sure that specific
    functionality isn’t already covered by built-in .NET Framework assemblies.
    It is better to use first-class .NET modifiers than attributes, as this
    simplifies the integration process with other assemblies.
  • What information must the attribute store?
    Is this
    attribute intended to be a simple flag to indicate a certain capability
    or will the attribute have to store information? An attribute can hold a
    set of information given to it at design time and expose that information
    at runtime. For an example, take a look at the Alias attribute in the sample application.
  • In which assembly should the attribute reside?
    In most cases,
    it is okay to include the attributes in the same assembly that will be
    using them. However, there are instances when it is better to place the
    attributes inside of a common, lightweight, shared assembly. This type of
    configuration allows clients to use the attributes without referencing
    unneeded assemblies.
  • Which assemblies will recognize the attribute?
    An attribute
    isn’t worth anything if there are no modules that read it. You will most
    likely place the classes that read the attribute inside of the same
    assembly in which the attributes reside. However, as mentioned above,
    there are instances when you want the logic that reads the attributes, and
    the attributes themselves, in different assemblies.

Using attributes

Before
we get into the details of how to create custom attributes, we need to look at
how they are used. For example, assume we have an attribute called “Hide”
which effectively hides properties so that they don’t print to the screen. If
we were to apply this attribute to the “SSN” property, our code would
look like Listing A.

Listing A

[Hide()]
publicstring SSN
{
get { return _ssn; }
set { _ssn = value; }
}

As a more complicated example, assume we have an attribute
called “Alias”. This attribute’s job is to determine the aliases a
property may have. This allows the property’s value to be mapped into another
property even if the property names don’t match. This attribute accepts a
series of string values to hold as the mapping names (Listing B).

Listing B

[Alias(“FirstName”, “First”)]
publicstring FName
{
get { return _fName; }
set { _fName = value; }
}

In this case, the
property “FName” is mapped to both “FirstName” and “First.”
See the example application for more detail on this type of usage.

Creating attributes

Creating attributes
is a simple process. You define a class with the data you want to store, and
inherit from the System.Attribute class. Listing
C
is an example of how to create the “Alias” attribute shown in
the previous section.

Listing C

classAlias : System.Attribute
{
string[] _names;

public Alias(paramsstring[] names)
{
this.Names = names;
}

publicstring[] Names
{
get { return _names; }
set { _names = value; }
}
}

As you can see, this
is just a normal class and, with the exception of inheriting from
System.Attribute, we didn’t have to do anything special to enable it to be an
attribute. We simply defined the constructor that needed to be used and created
a property and private member to store the data.

Listing D is a much simpler attribute — the “Hide”
attribute. This attribute requires no constructor (it uses the default) and
doesn’t store any data. This is because this attribute is simply a “flag”
type attribute:

Listing D

classHide : System.Attribute
{
//This is a simple attribute, that only requires
// the default constructor.
}

Reading attributes from code

Reading an
attribute and examining its data is significantly more complicated than either
using an attribute or creating an attribute. Reading an attribute requires the
developer to have a basic understanding of how to use reflection on an object.
If you are unfamiliar with reflection, you may want to read my “Applied reflection” article series.

Let’s assume that
we are examining a class and we want to determine which properties have the
Alias attribute applied and which aliases are listed. Listing E implements this logic:

Listing E

privateDictionary<string, string> GetAliasListing(Type destinationType)
{
//Get all the properties that are in the
// destination type.
PropertyInfo[] destinationProperties = destinationType.GetProperties();
Dictionary<string, string> aliases = newDictionary<string, string>();

foreach (PropertyInfo property in destinationProperties)
{
//Get the alias attributes.
object[] aliasAttributes =
property.GetCustomAttributes(typeof(Alias), true);

//Loop through the alias attributes and
// add them to the dictionary.
foreach (object attribute in aliasAttributes)
foreach (string name in ((Alias)attribute).Names)
aliases.Add(name, property.Name);

//We also need to add the property name
// as an alias.
aliases.Add(property.Name, property.Name);
}

return aliases;
}

The most important
lines of this section of code are where we call GetCustomAttributes and the
section where we loop through the attributes and extract the aliases.

The
GetCustomAttributes method is available from the PropertyInfo class that we
extracted from the object’s Type. In the usage shown above, we tell the
GetCustomAttributes method what type of attribute we’re looking for and also
pass “true” to enable it to pull inherited attributes. The
GetCustomAttributes method returns an object array if any matching attributes are
found. There is also another overload of the method that allows you to pull all
attributes on the property, regardless of the attribute’s type.

Once we have the
attributes, we need to examine them and extract the information we’re
looking for. This is done by looping through the objects in the array given to
us from GetCustomAttributes and casting each object into the type of attribute
we’re looking for. After the object has been cast, we can access properties of
the attribute the same way we would access properties of any other class.

As I previously
mentioned, reading the attribute is the hardest part. However, once you’ve
written the code to read an attribute, it is fairly easy to remember and
implement in the future.

In the sample application

I highly recommend
that you download the sample application included with this document. The sample
application implements the following attributes and shows you how to read/use
them in a simple Windows application:

  • Alias — This is the
    same Alias attribute mentioned above. This attribute is used when you want
    to translate one type of object into another type of object. For example,
    if you have a Customer object and an Address object, you could translate
    both of those into a combined Person object, which contains the person’s
    name and address. This would be used when a direct cast can’t be made.
  • DisplayName –– The sample
    application includes code that examines a class instance and prints its
    property names and values to the screen. This attribute is used to
    override what is sent to the screen for the property name. For example, a
    property named “FName” could have a DisplayName attribute
    applied to it so that it displays as “First Name”.
  • Examine — This
    attribute instructs the PrintObject() method in the sample application to
    go a level deeper and print out the property values for the property that
    has the Examine attribute applied. For example, the Customer object in the
    sample application has the Examine attribute applied to the Address
    property. This instructs the PrintObject method to print out all
    information in the address property.
  • Hide — This simply
    instructs the PrintObject() method to not print the current property out
    to the screen. This is used on the SSN property of the Customer object.

The sample application includes comments for every step it
takes in implementing and reading the attributes. Take a look and I’m sure you’ll
see some functionality you can take advantage of in your own applications.