18.1 Attributes

An attribute
is an object that represents data you want to associate with an
element in your program. The element to which you attach an attribute
is referred to as the target of that attribute.
For example, the attribute:

[NoIDispatch]

is associated with a class or an interface to indicate that the
target class should derive from IUnknown rather
than IDispatch when exporting to COM. COM
interface programming is discussed in detail in Chapter 22.

In Chapter 17, you saw this attribute:

[assembly: AssemblyKeyFile("c:\\myStrongName.key")]

This inserts metadata into the assembly to designate the
program's strong name.

18.1.1 Intrinsic Attributes

Attributes come in two flavors:
intrinsic
and custom.
Intrinsic attributes are supplied as part of the Common Language
Runtime (CLR), and they are integrated into .NET. Custom attributes
are attributes you create for your own purposes.

Most programmers will use only intrinsic attributes, though custom
attributes can be a powerful tool when combined with reflection,
described later in this chapter.

18.1.1.1 Attribute targets

If you
search through the CLR, you'll find a great many
attributes. Some attributes are applied to an assembly, others to a
class or interface, and some, such as [WebMethod],
are applied to class members. These are called the
attributetargets. Possible
attribute targets are detailed in Table 18-1.

You must place assembly attributes after all using
statements and before any code.

Many intrinsic attributes are used for interoperating with COM, as
discussed in detail in Chapter 22.
You've already seen use of one attribute
([WebMethod]) in Chapter 16.
You'll see other attributes, such as the
[Serializable] attribute, used in the discussion
of serialization in Chapter 19.

The System.Runtime namespace offers a number of
intrinsic attributes, including attributes for assemblies (such as
the keyname attribute), for configuration (such as
debug to indicate the debug build), and for
version attributes.

You can organize the intrinsic attributes by how they are used. The
principal intrinsic attributes are those used for COM, those used to
modify the Interface Definition
Language (IDL) file from within a source-code file, those used by the
ATL Server classes, and those used by the Visual C++ compiler.

Perhaps the attribute you are most likely to use in your everyday C#
programming (if you are not interacting with COM) is
[Serializable]. As you'll see in
Chapter 19, all you need to do to ensure that your
class can be serialized to disk or to the Internet is add the
[Serializable] attribute to the class:

[Serializable]
class MySerializableClass

The attribute tag is put in square brackets immediately before its
targetin this case, the class declaration.

The key fact about intrinsic attributes is that you know when you
need them; the task will dictate their use.

18.1.2 Custom Attributes

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.

18.1.2.1 Declaring an attribute

Attributes,
like most things in C#, are embodied in classes. To create a custom
attribute, derive your new custom attribute class from
System.Attribute:

public class BugFixAttribute : System.Attribute

You need to tell the compiler which kinds of elements this attribute
can be used with (the attribute target). Specify this with (what
else?) an attribute:

AttributeUsage is an attribute applied to
attributes: a meta-attribute. It provides, if
you will, meta-metadatathat 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
targetin 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.

18.1.2.2 Naming an attribute

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.

18.1.2.3 Constructing an attribute

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:

It is common to create read-only properties for the positional
parameters:

public int BugID
{
get
{
return bugID;
}
}

18.1.2.4 Using an attribute

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. 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.

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.
You'll see how to get at this metadata and use it in
your program in the next section.