.NET Development Foundation/Attributes
Annexes: Building custom attributes
editOriginal text for this page authored by William "Scott" Baker
Attributes Summary
editAttributes are a way to "tag" elements of your code's metadata with descriptive information that can be accessed at runtime using reflection. Attributes must derive from System.Attribute, either directly or indirectly. A multitude of attributes exist in the .NET framework; you can also define your own. There are three aspects to using attributes in your code:
- Defining a custom attribute class, which involves:
- Assigning the AttributeUsageAttribute attribute to your class.
- Writing the code to define your custom attribute class.
- Creating the parameters for your class.
- Assigning an attribute to a code member.
- Retrieving attribute information at runtime.
Creating a Custom Attribute Class
editAs previously mentioned, there are several predefined attributes in the .NET Framework; you may have already used them in your code. The XML parser in particular relies heavily on attributes when (de)serializing objects. You can also define your own custom attributes, as we will show here. Defining a custom attribute involves three steps:
- Assigning the AttributeUsageAttribute attribute to your class.
- Writing the code to define your custom attribute class.
- Creating the parameters for your class.
Assigning the "AttributeUsageAttribute" To Your Class
editNote: In Visual Basic, use of the AttributeUsageAttribute attribute is required on all custom attributes. Applying the AttributeUsageAttribute attribute to a class:
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] public Class QualityCheckAttribute : System.Attribute { // ... }
Notice the use of "AttributeUsage" vs. "AttributeUsageAttribute". By convention, all attributes are named with the "Attribute" suffix - but the suffix can be omitted when used in code. This holds true for user defined attributes as well; the QualityCheckAttribute attribute could be referenced as either:
[QualityCheck] // or... [QualityCheckAttribute]
The AttributeUsageAttribute has three members: ValidOn, AllowMultiple and Inherited.
- The ValidOn member accepts AttributeTargets enum values, and restricts your attribute to the code types you specify. The default value is AttributeTargets.All. You can confine your attribute to classes, enums, return values or any of the list below:
All (any element) Delegate GenericParameter Parameter Assembly Enum Interface Property Class Event Method ReturnValue Constructor Field Module* Struct *Module refers to a portable executable (.exe or .dll), and not a Visual Basic standard module.
You can also combine target types as a bitwise OR operation to specify multiple acceptable values:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
- AllowMultiple is a boolean value that determines whether or not an attribute can be applied more than once to a given member. The default value is false. The example below illustrates multiple instances of the same attribute on an element of code:
[QualityCheck("Scott Baker", "02/28/06", IsApproved = true, Comment = "This code follows all established guidelines. Release approved.")] [QualityCheck("Matt Kauffman", "01/15/06", IsApproved = false, Comment = "Code quality much improved. Minor revision required.")] [QualityCheck("Joe Schmoe", 01/01/06", IsApproved = false, Comment = "This code is a mess and needs a complete rewrite")] public class MyClass { // ... }
- The Inherited member determines whether attributes set on a class will be inherited by classes further down the inheritance tree. The default value is true:
[AttributeUsage(AttributeTargets.Class)] public class AttrOneAttribute : Attribute { // ... } // This attribute will not be inherited [AttributeUsage(AttributeTargets.Class, Inherited = false)] public class AttrTwoAttribute : Attribute { // ... } [AttrOne] [AttrTwo] public class ClassOne { // ... } // This class inherits AttrOne from ClassOne, // but not AttrTwo public class ClassTwo : ClassOne { // ... }
Defining A Custom Attribute Class
edit- Attributes are classes that inherit from System.Attribute, either directly or indirectly:
public Class QualityCheckAttribute : System.Attribute // direct { // ... } public Class FinalCheck : QualityCheckAttribute // indirect { // ... }
- Attribute classes have the AttributeUsageAttribute attribute:
[AttributeUsage(AllowMultiple = true, Inherited = false)] public Class QualityCheckAttribute : System.Attribute { // ... }
As previously mentioned, the use of AttributeUsageAttribute is required in VB. In C#, it is automatically applied with the default values if not declared.
Creating Parameters For An Attribute
editPositional Parameters
editpublic class QualityCheckAttribute : Attribute { public QualityCheckAttribute(string Name, string Date) // ... }
Named Parameters
editpublic class QualityCheckAttribute : Attribute { private string _name; private string _date; private bool isApproved; public bool IsApproved { get {return isApproved;} set {isApproved = value;} } public QualityCheckAttribute(string Name, string Date) { // ... } }
Keep in mind that a variable in your code can be both a positional parameter and a named parameter. If we were to add public properties for the _name
and _date
fields we could use them either as named parameters or positional parameters. Of course, this is not a recommended practice: required parameters should be positional and optional parameters should be named.
Assigning An Attribute To A Code Member
editYou have already seen examples of assigning an attribute to a code member. However, there are some points that must be clarified.
- Disambiguation is the clarification of the use of an attribute on a code member.
- Syntax - there is more than one way to apply multiple attributes.
Disambiguation
editpublic class MyAttribute : Attribute { [SomeAttribute("Hello")] public string MyMethod(aString) { return aString; } }
Disambiguation resolves these issues. By specifying the code type the attribute is applied to, we are able to resolve the confusion. The code below shows that the attribute applies to the return value:
public class MyAttribute : Attribute { [return : SomeAttribute] public string MyMethod(aString) { return aString; } }
The table below lists all declarations where attributes are allowed; for each declaration, the possible targets for attributes on the declaration are listed in the second column. Targets in bold are the defaults.
Declaration Possible targets assembly assembly module module class type struct type interface type enum type delegate type, return method method, return parameter param field field property — indexer property property — get accessor method, return property — set accessor method, param, return event — field event, field, method event — property event, property event — add method, param event — remove method, param *Reference: Disambiguating Attribute Targets (C# Programming Guide)
One would think that the AttributeUsageAttribute's AttributeTargets in an attribute's definition would help to prevent this confusion: one would be wrong. The compiler does not use the AttributeUsageAttribute information when resolving conflicts. Even if you define an attribute to apply only to a specific type, for instance AttributeTargets.Return, you must still clarify that it applies to the return type when applying the attribute or the compiler will use the default target method type, and throw an error.
Syntax: Applying Multiple Attributes
edit[AttrOne(...), AttrTwo(...)] // or... [AttrOne(...)] [AttrTwo(...)]
The two are equivalent. Keep in mind that if you are going to specify more than one attribute in a single brace, they must apply to the same target type. If not, you must give each type a separate declaration:
[return : AttrOne(...), method : AttrTwo(...)] // <-- invalid! // instead, you must... [return : AttrOne(...)] [method : AttrTwo(...)]
Retrieving Attribute Information At Runtime
editBeing able to declare and apply attributes is not very helpful unless we can retrieve that data and do something with it. Fortunately, its a straightforward process. There are three basic scenarios that will be addressed:
- Retrieve a single attribute from a member.
- Retrieve multiple attributes from a member.
- Retrieve attributes of a single type from multiple members.
Retrieving A Single Attribute From A Member
editTo access attribute information:
- Declare an instance of the attribute type.
- Use the Attribute.GetCustomAttribute(type, typeof) method to read the attribute into the instance.
- Use the properties of the instance to read the values.
The example code below declares a class ExampleClass with a QualityCheck attribute. The GetSingleAttribute method accepts a target member type and the type of attribute you're looking for. The Attribute.GetCustomAttribute method retrieves the attribute information into the attr object, from which we can read the all-important IsApproved property:
[QualityCheck("Scott Baker", "02/04/2006", IsApproved = false)] public class ExampleClass { public static void Main() { GetSingleAttribute(typeof(ExampleClass), typeof(QualityCheck)) } public static void GetSingleAttribute(Type targetType, Type attrType) { typeof(attrType) attr = (attrType)Attribute.GetCustomAttribute(targetType, typeof(attrType)); if (attr == null) { //... } else { Console.Writeline(attr.IsApproved); } }
An important factor to keep in mind is that the GetCustomAttribute method is designed to read one and only one attribute. GetCustomAttribute actually checks to see if more than one attribute matches - if there is no match it returns null, but if there is more than one match it will throw an AmbiguousMatchException. When checking for attributes the only time it is safe to use GetCustomAttribute is when the attribute's definition states [AttributeUsage(AllowMultiple=false)].
Retrieving Multiple Attributes From A Member
editReading multiple instances of an attribute on a member isn't much different than reading one; to read multiple attributes use the plural GetCustomAttributes, which returns an array of attributes. You can then iterate through the resultant array and read the values:
QualityCheck[] attrArray = (QualityCheck[])Attribute.GetCustomAttributes(t, typeof(QualityCheck)); foreach (QualityCheck attr in attrArray) { Console.Writeline(attr.IsApproved); }
Retrieving A Single Attribute Type From Multiple Members
editWhat if you want to do something a little more complex, like checking each method in a class for a QualityCheck attribute? Thanks to the System.Reflection namespace, we don't have to even break a sweat. Simply read all the members (methods, in this instance) into a MemberInfo array and iterate through them:
using System.Reflection public class ExampleClass { public static void Main() { RetrieveAttributes(typeof(ExampleClass)); } public void RetrieveAttributes(Type t) { MemberInfo[] methodList = t.GetMethods(); foreach (MemberInfo m in methodList) { QualityCheck[] attrArray = (QualityCheck[])Attribute.GetCustomAttributes(m, typeof(QualityCheck)); foreach (QualityCheck attr in attrArray) { Console.Writeline(attr.IsApproved); } } } }