BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Reflection
/NET 1.1+

Creating Custom Attributes

The twenty-second and final part of the Reflection tutorial describes the creation and application of custom attributes. It also explains how those attributes can be configured to ensure that they are applied and inherited correctly.

Named Parameters

If you want your attribute to include optional properties, these should not be included in attribute's constructor. You set the values of such properties using named parameters when you apply the attribute to a code item. To demonstrate we'll first update our Data attribute to give it a new property. This will be a Boolean that specifies whether the information in an object will be retrieved only when it is first accessed, using lazy loading, or ahead of time.

public class DataAttribute : Attribute
{
    public DataAttribute(string table)
    {
        Table = table;
    }

    public string Table { get; set; }
    public bool UseLazyLoading { get; set; }
}

To apply an attribute and set its optional properties, the named parameters are added after the required values in the constructor. Named parameters are delimited using commas and are set by providing the property name and value, separated with an equals sign.

[Data("People", UseLazyLoading=true)]
public class Person
{
    public string Name { get; set; }
}

Let's reflect over the Person class again to read the optional attribute property.

Type type = typeof(Person);
object[] attributes = type.GetCustomAttributes(typeof(DataAttribute), false);
DataAttribute attr = attributes[0] as DataAttribute;
Console.WriteLine(attr.Table);
Console.WriteLine(attr.UseLazyLoading);

/* OUTPUT

People
True

*/

Specifying Valid Attribute Targets

The behaviour of attributes and the way in which they may be used can be controlled by applying the AttributeUsage attribute to the custom attribute class. One of the key modifications that you can make is specifying the type of code to which you can apply your attribute. By default, custom attributes can be applied to assemblies, modules, interfaces, classes, structures, enumerations, delegates, constructors, events, fields, methods, properties, parameters and return values. Most attributes will only be valid for a subset of these possibilities.

To specify which elements an attribute is valid for, you pass a value of the AttributeTargets enumeration. This is an enumeration with the Flags attribute, allowing you to combine multiple targets using the logical OR operator. It has a constant for each applicable attribute target. If you wish to be able to apply your attribute to any target, you can use the AttributeTargets.All value.

Our Data attribute is designed for use with classes and structures so adjust it as follows.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class DataAttribute : Attribute
{
    public DataAttribute(string table)
    {
        Table = table;
    }

    public string Table { get; set; }
    public bool UseLazyLoading { get; set; }
}

Single and Multiple Application

Some attributes can be attached multiple times to the same code element. Others only make sense when they can only be applied once per item. By default, your custom attributes will only be able to be applied once but this can be changed using the AllowMultiple property of the AttributeUsage attribute. This accepts a Boolean value via a named parameter. As our test attribute should only be used once per class or structure, we don't need to change this behaviour. However, if we wanted to explicitly state its single usage we could do so by modifying its attribute as follows:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false)]

Attribute Inheritance

As we have seen in the previous article, some attributes are automatically inherited, so when applied to a base class, they are also applied to its subclasses. Others, such as the Serializable attribute, are not inherited and must be applied to subclasses individually as required. To determine which of these options should be applied to your own attributes, set the Inherited property of the AttributeUsage attribute.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true)]

NB: The default option is false, indicating that attributes are not inherited.

Attribute Usage Example

To complete this article, and indeed the Reflection tutorial, we'll create a final example that uses a number of reflection techniques, including the use of custom attributes. We'll create a pair of attributes that can be applied to classes or structures and their properties. These attributes will be used to define how a fixed-column string can be generated from an object of the type they decorate. Later we'll create an abstract class that uses the attributes within an overridden ToString method to construct the fixed-column string. Finally, we'll create a class that inherits from our base class that has the attributes defined to test that the fixed-column data is constructed correctly.

27 August 2012