You’re already familiar with the notion of attributing code elements of a program with
modifiers, such as virtual
or ref
. These constructs are built into the language.
Attributes are an extensible mechanism for adding custom information
to code elements (assemblies, types, members, return values, and parameters). This
extensibility is useful for services that integrate deeply into the type system, without
requiring special keywords or constructs in the C# language.
A good scenario for attributes is serialization—the process of converting arbitrary objects to and from a particular format.
In this scenario, an attribute on a field can specify the translation between C#’s representation of the field and the format’s representation of the field.
An attribute is defined by a class that inherits (directly or indirectly) from the
abstract class System.Attribute
. To attach an attribute
to a code element, you specify the attribute’s type name in square brackets, before the
code element. For example, the following attaches the ObsoleteAttribute
to the Foo
class:
[ObsoleteAttribute]
public class Foo { ... }
This attribute is recognized by the compiler and will cause compiler warnings if a type or member marked obsolete is referenced. By convention, all attribute types end in the word “Attribute.” C# recognizes this and allows you to omit the suffix when attaching an attribute:
[Obsolete]
public class Foo { ... }
ObsoleteAttribute
is a type declared in the
System
namespace as follows (simplified for
brevity):
public sealed class ObsoleteAttribute : Attribute { ... }
Attributes may have parameters. In the
following example, we apply the XmlElement
attribute to
a class. The XmlElement
attribute tells the System.Xml.Linq
model how an object is represented in XML. The
XmlElement
attribute accepts several
attribute parameters. The following attribute maps the CustomerEntity
class to an XML element named Customer
, belonging to the http://oreilly.com
namespace:
[XmlElement ("Customer", Namespace="http://blah")]
public class CustomerEntity { ... }
Attribute parameters fall into one of two categories: positional and named. In the preceding example, the first argument is a positional parameter; the second is a named parameter. Positional parameters correspond to parameters of the attribute type’s public constructors. Named parameters correspond to public fields or public properties on the attribute type.
When specifying an attribute, you must include positional parameters that correspond to one of the attribute’s constructors. Named parameters are optional.
Implicitly, the target of an attribute is the code element it immediately precedes, which is typically a type or type member. You can also attach attributes, however, to an assembly. This requires that you explicitly specify the attribute’s target.
Here is an example of using the CLSCompliant
attribute to specify CLS compliance for an entire assembly:
[assembly:
CLSCompliant(true)]
Multiple attributes can be specified for a single code element. Each attribute can be listed either within the same pair of square brackets (separated by a comma), or in separate pairs of square brackets (or a combination of the two).
The following three examples are semantically identical:
[Serializable, Obsolete, CLSCompliant(false)] public class Bar {...} [Serializable] [Obsolete] [CLSCompliant(false)] public class Bar {...} [Serializable, Obsolete] [CLSCompliant(false)] public class Bar {...}
You can define your own by subclassing System.Attribute
. For example, we could use the following custom attribute
for flagging a method for unit
testing:
[AttributeUsage (AttributeTargets.Method)] public sealed class TestAttribute : Attribute { public int Repetitions; public string FailureMessage; public TestAttribute () : this (1) { } public TestAttribute (int repetitions) { Repetitions = repetitions; } }
Here’s how we could apply the attribute:
class Foo { [Test] public void Method1() { ... } [Test(20)] public void Method2() { ... } [Test(20, FailureMessage="Debugging Time!")] public void Method3() { ... } }
AttributeUsage
is itself an attribute that
indicates the construct (or combination of constructs) that the custom attribute can be
applied to. The AttributeTargets
enum includes such
members as Class, Method, Parameter, Constructor
(and
All
, which combines all targets).
There are two standard ways to retrieve attributes at runtime:
These latter two methods are overloaded to accept any reflection object that
corresponds to a valid attribute target (Type, Assembly, Module,
MemberInfo
, or ParameterInfo
).
Here’s how we can enumerate each method in the preceding Foo
class that has a TestAttribute
:
foreach (MethodInfo mi in typeof (Foo).GetMethods()) { TestAttribute att = (TestAttribute) Attribute.GetCustomAttribute (mi, typeof (TestAttribute)); if (att != null) Console.WriteLine ( "Method {0} will be tested; reps={1}; msg={2}", mi.Name, att.Repetitions, att.FailureMessage); }
Here’s the output:
Method Method1 will be tested; reps=1; msg= Method Method2 will be tested; reps=20; msg= Method Method3 will be tested; reps=20; msg=Debugging Time!