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, 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
XmlElementAttribute
to a class. This
attribute tells XML serializer (in System.Xml.Serialization
) how an object is
represented in XML and 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://oreilly.com")]
public class CustomerEntity { ... }
Attribute parameters fall into one of two categories: positional or 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 Common
Language Specification (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 two examples are semantically identical:
[Serializable, Obsolete, CLSCompliant(false)] public class Bar {...} [Serializable] [Obsolete] [CLSCompliant(false)] public class Bar {...}
You can define your own attributes 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:
Call GetCustomAttributes
on
any Type
or MemberInfo
object.
Call Attribute.GetCustomAttribute
or Attribute.GetCustomAttributes
.
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); }
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!