From Pro C# 2008 and the .NET 3.5 Platform, Fourth Edition by Andrew Troelsen
Understanding Attributed Programming
One role of a .NET compiler is to generate metadata
descriptions for all defined and referenced types. In addition to this standard metadata contained
within any assembly, the .NET platform provides a way for programmers to embed additional
metadata into an assembly using attributes. In a nutshell, attributes are nothing more than code
annotations that can be applied to a given type (class, interface, structure, etc.), member (property,
method, etc.), assembly, or module.
The idea of annotating code using attributes is not new. COM IDL provided numerous predefined
attributes that allowed developers to describe the types contained within a given COM server.
However, COM attributes were little more than a set of keywords. If a COM developer needed to
create a custom attribute, he or she could do so, but it was referenced in code by a 128-bit number
(GUID), which was cumbersome at best.
Unlike COM IDL attributes (which again were simply keywords), .NET attributes are class types
that extend the abstract System.Attribute base class. As you explore the .NET namespaces, you will
find many predefined attributes that you are able to make use of in your applications. Furthermore,
you are free to build custom attributes to further qualify the behavior of your types by creating a
new type deriving from Attribute.
Understand that when you apply attributes in your code, the embedded metadata is essentially
useless until another piece of software explicitly reflects over the information. If this is not the case,
the blurb of metadata embedded within the assembly is ignored and completely harmless.
Attribute Consumers
As you would guess, the .NET 3.5 Framework SDK ships with numerous utilities that are indeed on
the lookout for various attributes. The C# compiler (csc.exe) itself has been preprogrammed to
discover the presence of various attributes during the compilation cycle. For example, if the C#
compiler encounters the [CLSCompliant] attribute, it will automatically check the attributed item to
ensure it is exposing only CLS-compliant constructs. By way of another example, if the C# compiler
discovers an item attributed with the [Obsolete] attribute, it will display a compiler warning in the
Visual Studio 2008 Error List window.
In addition to development tools, numerous methods in the .NET base class libraries are preprogrammed
to reflect over specific attributes. For example, if you wish to persist the state of an
object to file, all you are required to do is annotate your class with the [Serializable] attribute. If
the Serialize() method of the BinaryFormatter class encounters this attribute, the object is automatically
persisted to file in a compact binary format.
The .NET CLR is also on the prowl for the presence of certain attributes. Perhaps the most
famous .NET attribute is [WebMethod]. If you wish to expose a method via HTTP requests and automatically
encode the method return value as XML, simply apply [WebMethod] to the method and the
CLR handles the details. Beyond web service development, attributes are critical to the operation of
the .NET security system, Windows Communication Foundation, and COM/.NET interoperability
(and so on).
Finally, you are free to build applications that are programmed to reflect over your own custom
attributes as well as any attribute in the .NET base class libraries. By doing so, you are essentially
able to create a set of “keywords” that are understood by a specific set of assemblies.
Applying Attributes in C#
The .NET base class library provides a number of attributes in various
namespaces. Below is a snapshot of some—but by absolutely no means all—predefined
attributes.
A Tiny Sampling of Predefined Attributes
[CLSCompliant]
Enforces the annotated item to conform to the rules of the Common
Language Specification (CLS). Recall that CLS-compliant types are
guaranteed to be used seamlessly across all .NET programming languages.
[DllImport]
Allows .NET code to make calls to any unmanaged C- or C++-based code
library, including the API of the underlying operating system. Do note that
[DllImport] is not used when communicating with COM-based software.
[Obsolete]
Marks a deprecated type or member. If other programmers attempt to use
such an item, they will receive a compiler warning describing the error of
their ways.
[Serializable]
Marks a class or structure as being “serializable,” meaning it is able to persist
its current state into a stream.
[NonSerialized]
Specifies that a given field in a class or structure should not be persisted
during the serialization process.
[WebMethod]
Marks a method as being invokable via HTTP requests and instructs the CLR
to serialize the method return value as XML.
Building Custom Attributes
The first step in building a custom attribute is to create a new class deriving from System.Attribute. Example:
// A custom attribute.
public sealed class VehicleDescriptionAttribute : System.Attribute
{
private string msgData;
public VehicleDescriptionAttribute(string description)
{
msgData = description;
}
public VehicleDescriptionAttribute() { }
public string Description
{
get { return msgData; }
set { msgData = value; }
}
}
As you can see, VehicleDescriptionAttribute maintains a private internal string (msgData)
that can be set using a custom constructor and manipulated using a type property (Description).
Beyond the fact that this class derived from System.Attribute, there is nothing unique to this class
definition.
For security reasons, it is considered a .NET best practice to design all custom attributes as sealed. In
fact, Visual Studio 2008 provides a code snippet named Attribute that will dump out a new System.
Attribute-derived class into your code window.