.NET Development Foundation/Attributes

From Wikibooks, open books for an open world
Jump to navigation Jump to search


Reflexion: Building custom attributes


Annexes: Building custom attributes[edit | edit source]

Original text for this page authored by William "Scott" Baker

Attributes Summary[edit | edit source]

Attributes 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:

  1. Defining a custom attribute class, which involves:
    1. Assigning the AttributeUsageAttribute attribute to your class.
    2. Writing the code to define your custom attribute class.
    3. Creating the parameters for your class.
  2. Assigning an attribute to a code member.
  3. Retrieving attribute information at runtime.

Creating a Custom Attribute Class[edit | edit source]

As 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:

  1. Assigning the AttributeUsageAttribute attribute to your class.
  2. Writing the code to define your custom attribute class.
  3. Creating the parameters for your class.

Assigning the "AttributeUsageAttribute" To Your Class[edit | edit source]

The "scope" and other characteristics of your attribute should be specified by the use of the AttributeUsageAttribute attribute.
Note: 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 | edit source]

  • 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[edit | edit source]

Attributes accept two types of parameters: positional and named. Positional parameters are defined by public constructors in your class, must be in the order defined and are required; named parameters are defined by public properties, can be in any order and are optional.
Positional Parameters[edit | edit source]
Positional parameters for an attribute are defined by the constructors in the class. As with any class, constructors can be overloaded and a default constructor that takes no parameters can be defined. As with any other class, the signature of the positional parameters must match the signature of a constructor in the attribute class:
public class QualityCheckAttribute : Attribute
{
  public QualityCheckAttribute(string Name, string Date)
  // ... 
}
Named Parameters[edit | edit source]
Named parameters for an attribute are defined by public properties defined in the class. Named parameters are optional and are declared after any positional parameters. The IsApproved property demonstrates a named parameter:
public 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[edit | edit source]

You 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[edit | edit source]

The code below isn't clear as to whether SomeAttribute applies to the method MyMethod or to its return value:
public 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 | edit source]

When applying more than one attribute to a member, there are two ways to do so:
[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[edit | edit source]

Being 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:

  1. Retrieve a single attribute from a member.
  2. Retrieve multiple attributes from a member.
  3. Retrieve attributes of a single type from multiple members.

Retrieving A Single Attribute From A Member[edit | edit source]

To access attribute information:

  1. Declare an instance of the attribute type.
  2. Use the Attribute.GetCustomAttribute(type, typeof) method to read the attribute into the instance.
  3. 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[edit | edit source]

Reading 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[edit | edit source]

What 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);
      }
    }
  }
}

External links[edit | edit source]