Implementing Custom Attributes


Implementing a custom attribute (and the accompanying reflection code to make use of it) is easy and straightforward. For example, suppose you want to provide a custom attribute that adds a color option to your classes and interfaces. The color is an enum defined as:

     public enum ColorOption {Red,Green,Blue};

The color attribute should also have:

  • A default constructor that assigns ColorOption.Red to the target class or interface

  • A parameterized constructor that accepts the color to assign to the target class or interface

  • A property that accepts the color to assign to the target class or interface

Example C-3 shows the implementation of the ColorAttribute class.

Example C-3. Implementing a custom attribute
 public enum ColorOption {Red,Green,Blue}; [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)] public class ColorAttribute : Attribute {    ColorOption m_Color;    public ColorOption Color    {       get       {          return m_Color;       }       set       {          m_Color = value;       }    }    public ColorAttribute(  )    {       Color = ColorOption.Red;    }    public ColorAttribute(ColorOption color)    {       this.Color = color;    } }

Before walking though Example C-3, here are a few examples that use the ColorAttribute custom attribute:

  • Using the default constructor:

     [Color] public class MyClass1 {}

  • Using the parameterized constructor:

     [Color(ColorOption.Green)] public class MyClass2 {}

  • Using the property:

     [Color(Color = ColorOption.Blue)] public class MyClass3 {}

  • Using it on an interface:

     [Color] public interface IMyInterface {}

As you can see in Example C-3, there isn't much to implementing a custom attribute. The ColorAttribute class derives from the Attribute class and defines the public Color property to access the m_Color member variable. The AttributeUsage attribute indicates that the Color attribute can be applied only to classes or interfaces:

     [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)]     public class ColorAttribute : Attribute     {...}

As a result, trying to use the Color attribute on any other target (such as a method) doesn't compile:

     public class MyClass     {        [Color]//This will not compile        public void MyMethd(  )        {}     }

The definition of AttributeUsageAttribute is:

     public sealed class AttributeUsageAttribute : Attribute     {         public AttributeUsageAttribute(AttributeTargets validOn);         public bool AllowMultiple { get; set; }         /* Other members */     }

Its constructor accepts an enum of type AttributeTargets, letting it know on which types this attribute is valid. The AttributeTargets enum offers a number of values, such as AttributeTargets.Method, AttributeTargets.Assembly, and so on.

The other interesting property of AttributeUsage is AllowMultiple. By default, AllowMultiple is set to false, so you can apply the Color attribute only once per class or interface. As a result, this usage doesn't compile:

     [Color(ColorOption.Green)]     [Color]     public class MyClass     {}

However, if you explicitly set AllowMultiple to TRue, you can use the Color attribute multiple times on the same target:

     [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface,                                                                AllowMultiple = true)]     public class ColorAttribute : Attribute     {...}

Reflecting Custom Attributes

When you write code to reflect attributes, it's often better to do so in the form of a static helper method that accepts an object or a type to reflect and returns the information, as shown in Example C-4. That way, other parties in your application can use the method, without bothering with instantiating an object first.

Example C-4. Reflecting a custom attribute
 public static ColorOption GetColor(object obj) {    Type objType = obj.GetType(  );    Debug.Assert(objType.IsClass || objType.IsInterface);    Type attribType = typeof(ColorAttribute);    object[] attributeArray = objType.GetCustomAttributes(attribType,true);    //Only one color attribute at the most    Debug.Assert(attributeArray.Length == 0 || attributeArray.Length == 1);    if(attributeArray.Length == 0)    {       return ColorOption.Red;    }    ColorAttribute colorAttribute = (ColorAttribute)attributeArray[0];    return colorAttribute.Color; }

The GetColor( ) method in Example C-4 accepts an object as a parameter and constructs a Type representing the type of the parameter, verifying that it got either a class or an interface:

     Debug.Assert(objType.IsClass || objType.IsInterface);

It then calls the GetCustomAttributes( ) method of Type to get an array of all the attributes of the specified attribute type. Note that GetCustomAttributes( ) returns all the attributes associated with the type, not just the custom attributes. A better name for it would be simply GetAttributes( ). After retrieving the array of attributes of type ColorAttribute, the GetColor( ) method verifies that the size of the array is either 0 or 1, because there can be at most one color attribute associated with the object. If no attribute is associated with the object, the method returns ColorOption.Red. However, if one such attribute is present, the method returns its Color value.

What you choose to do with the knowledge of the color of the object is entirely domain-specific. All .NET provides is an easy-to-use and extensible mechanism for associating a custom attribute with a type and reflecting it.



Programming. NET Components
Programming .NET Components, 2nd Edition
ISBN: 0596102070
EAN: 2147483647
Year: 2003
Pages: 145
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net