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 with 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 XmlSerializer
(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
, and Constructor
(as well as 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 ( "{0} will be tested; reps={1}; msg={2}", mi.Name, att.Repetitions, att.FailureMessage); }
Here’s the output:
Method1 will be tested; reps=1; msg= Method2 will be tested; reps=20; msg= Method3 will be tested; reps=20; msg=Debugging Time!