Attributes


You have seen .NET Framework-defined attributes used a few times already in this book. For example:

  • In Chapter 8 you used the Serializable attribute to enable serialization for a managed (__gc) class.

  • In Chapter 15 you used the WebService and WebMethod attributes to enable a class and a method to be Web services.

  • In Chapter 16 you used the ThreadStatic attribute to make a static variable unique in each thread.

The overriding theme in every .NET Framework attribute is that it changes the way the code normally functions. Basically, you can think of attributes as declarative tags that are written to an assembly at compile time to annotate or mark up a class and/or its members so that class and/or its members can be later extracted at runtime, possibly to change its normal behavior.

To add an attribute to a class or its members, you add code in front of the element you want to annotate with the following syntax:

 [AttributeName(ConstructorArguments, optionalpropertyname=value)] 

If you want to add more than one attribute, you simply add more than one attribute within the square brackets, delimited by commas:

 [Attribute1(), Attribute2()] 

An important feature to you (other than the changed behavior caused by the .NET Framework attributes) is that you can access attributes using reflection.

A more important feature is that you can create your own custom attributes.

Creating a Custom Attribute

According to the Microsoft documentation, a custom attribute is just a class that is derived from the System::Attribute class with a few minor additional criteria. However, I found (by accident) that even if you don't derive from System::Attribute, it still seems to work. I recommend that you inherit from System::Attribute just to be safe, though.

The additional criteria are as follows:

  • The custom attribute class needs to be public.

  • By convention, the attribute name should end in "Attribute". A neat thing is that when you implement the attribute, you don't have to add the trailing "Attribute", as it's automatically added.

  • There's an additional AttributeUsageAttribute that you can apply to your custom attribute.

  • All properties that will be written to the metadata need to be public.

  • The properties available to be written to the metadata are restricted to Integer type (Byte, Int32, and so on), floating point (Single or Double), Char, String, Boolean, or Enum. Note that this means the very common DateTime data type isn't supported. (I show you how to get around this limitation later in this chapter.)

Of all the additional criteria, the only one you need to look at in more detail is the AttributeUsageAttribute attribute. This attribute controls the manner in which the custom attribute is used. To be more accurate, it defines three behaviors: which data types the custom attribute is valid on, if the custom attribute is inherited, and whether more than one of the custom attributes can be applied to a single data type.

You can specify that the custom attribute can be applied to any assembly entity (see Table 17-4) by giving the AttributeUsageAttribute attribute an AttributeTargets::All value. On the other hand, if you want to restrict the custom attribute to a specific type or a combination of types, then you would specify one or a combination (by ORing) of the AttributeTargets enumerations in Table 17-4.

Table 17-4: AttributeTargets Enumeration

All

Assembly

Class

Constructor

Delegate

Enum

Event

Field

Interface

Method

Module

Parameter

Property

ReturnValue

Struct

The second parameter of the AttributeUsageAttribute attribute specifies whether any class that inherits from a class that implements the custom attribute inherits that custom attribute. The default is that a class does inherit the custom attribute.

The final parameter allows a custom attribute to be applied more than one time to a single type. The default is that only a single custom attribute can be applied.

There are three ways that you can have data passed into the attribute when implementing. The first is by the custom attribute's construction. The second is by a public property. The third is by a public member variable.

Listing 17-3 shows the creation of two custom documentation attributes. The first is the description of the element within the class, and the second is a change history. By nature you should be able to apply both of these attributes to any type within a class and you should also have the attributes inherited. These attributes mostly differ in that a description can be applied only once to an element in a class, whereas the change history will be used repeatedly.

Listing 17-3: Documentation Custom Attributes

start example
 using namespace System; using namespace System::Text; using namespace System::Reflection; namespace Documentation {     [AttributeUsage(AttributeTargets::All, Inherited=true, AllowMultiple=false)]     public __gc class DescriptionAttribute : public Attribute     {         String *mAuthor;         DateTime mCompileDate;         String *mDescription;     public:         DescriptionAttribute(String *Author, String *Description)         {             mAuthor = Author;             mDescription = Description;             mCompileDate = DateTime::Now;         }         __property String *get_Author()         {             return mAuthor;         }         __property String *get_CompileDate()         {             return mCompileDate.ToShortDateString();         }         __property String *get_Description()         {            return mDescription; }         };     [AttributeUsage(AttributeTargets::All, Inherited=true, AllowMultiple=true)]     public __gc class HistoryAttribute : public Attribute     {         String *mAuthor;         DateTime mModifyDate;         String *mDescription;     public:         HistoryAttribute(String *Author, String *Description)         {             mAuthor = Author;             mDescription = Description;             mModifyDate = DateTime::Now;         }         __property String *get_Author()         {             return mAuthor;         }         __property String *get_ModifyDate()         {             return mModifyDate.ToShortDateString();         }         __property void set_ModifyDate(String *value)         {             mModifyDate = Convert::ToDateTime(value);         }         __property String *get_Description()         {             return mDescription;         }     }; } 
end example

As you can see by the code, other than the [AttributeUsage] attribute (which is inherited from System::Attribute), there is nothing special about these classes. They are simply classes with a constructor and a few public properties and private member variables.

The only thing to note is the passing of dates in the form of a string, which are then converted to DateTime structure. Attributes are not allowed to pass the DateTime structure as pointed out previously, so this simple trick fixes this problem.

Implementing a Custom Attribute

As you can see in the following example (see Listing 17-4), you implement custom attributes in the same way as you do .NET Framework attributes. In this example, the DescriptionAttribute attribute you created earlier is applied to two classes, a constructor, a member method, and a property. Also, the HistoryAttribute attribute is applied twice to the first class and then later to the property.

Listing 17-4: Implementing the Description and History Attributes

start example
 using namespace System; using namespace Documentation; namespace DocTestLib {     [Description(S"Stephen Fraser",         S"This is TestClass1 to test the documentation Attribute.")]     [History(S"Stephen Fraser",         S"Original Version.", ModifyDate=S"11/27/02")]     [History(S"Stephen Fraser",         S"Added DoesNothing Method to do nothing.")]     public __gc class TestClass1     {         String *mVariable;     public:         [Description(S"Stephen Fraser",             S"This is default constructor for TextClass1.")]         TestClass1()         {         }         [Description(S"Stephen Fraser",             S"This is method does nothing for TestClass1.")]         void DoesNothing()         {         }         [Description(S"Stephen Fraser",             S"Added Variable property.")]         [History(S"Stephen Fraser",             S"Removed extra CodeDoc Attribute")]         __property String *get_Variable()         {              return mVariable;         }        __property void set_Variable(String *value)         {             mVariable = value;         }     };     [Description(S"Stephen Fraser",         S"This is TestClass2 to test the documentation Attribute.")]     public __gc class TestClass2     {     }; } 
end example

Notice in Listing 17-4 that "Attribute" is stripped off the end of the attributes. This is optional and it is perfectly legal to keep "Attribute" on the attribute name.

Another thing that you might want to note is how to implement a named property to an attribute. This is done in the first use of the History attribute where I specify the date that the change was made:

 [History(S"Stephen Fraser", S"Original Version.", ModifyDate=S"11/27/02")] 

The modified date is also a string and not a DateTime as you would expect. This is because (as I pointed out previously) it is not legal to pass a DateTime to an attribute.

Using a Custom Attribute

You looked at how to use custom attributes when you learned about reflection. Custom attributes are just placed as metadata onto the assembly and, as you learned in reflection, it is possible to examine an assembly's metadata.

The only new thing about assembly reflection and custom attributes is that you need to call the GetCustomAttribute() method to get a specific custom attribute or the GetCustomAttributes() method to get all custom attributes for a specific type.

The tricky part with either of these two methods is that you have to typecast them to their appropriate type as both return an Object type. What makes this tricky is that you need to use the full name of the attribute or, in other words, unlike when you implemented it, you need the "Attribute" suffix added. If you created a custom attribute that doesn't end in "Attribute" (which is perfectly legal, I might add), then this won't be an issue.

Both of these methods have a few overloads, but they basically break down to one of three syntaxes. To get all custom attributes:

 public: Object *GetCustomAttributes(Boolean useInhertiance); // For example: Object *CustAttr[] = info->GetCustomAttributes(true); 

To get all of a specific type of custom attribute:

 public: Object *GetCustomAttributes(Type *type, Boolean useInhertiance); // For example: Object *CustAttr[] = info->GetCustomAttributes(__typeof(HistoryAttribute),true); 

Or to get a specific attribute for a specific type reference:

 public: static Attribute* GetCustomAttribute(<ReflectionReference>*, Type*); // For Example Attribute *attribute =     Attribute::GetCustomAttribute(methodInfo, __typeof(DescriptionAttribute)); 

Caution

If the type allows multiple custom attributes of a single type to be added to itself, then the GetCustomAttribute() method returns an Array and not an Attribute.

Listing 17-5 is really nothing more than another example of assembly reflection, except this time it uses an additional GetCustomAttribute() and GetCustomAttributes() method. The example simply walks through an assembly that you passed to it and displays information about any class, constructor, method, or property that is found within it. Plus, it shows any custom Description or History attributes that you may have added.

Listing 17-5: Using Custom Attributes to Document Classes

start example
 using namespace System; using namespace Reflection; using namespace Documentation; void DisplayDescription(Attribute *attr) {     if (attr != 0)     {         DescriptionAttribute *cd = dynamic_cast<DescriptionAttribute*>(attr);         Console::WriteLine(S"  Author: {0}  -  Compiled: {1}",             cd->Author, cd->CompileDate);         Console::WriteLine(S"  Description: {0}", cd->Description);         Console::WriteLine(S"    ---- Change History ----");     }     else         Console::WriteLine(S" No Documentation"); } void DisplayHistory(Object *attr[]) {     if (attr->Length > 0)     {         for (Int32 i = 0; i < attr->Length; i++)         {             HistoryAttribute *cd = dynamic_cast<HistoryAttribute*>(attr[i]);             Console::WriteLine(S"    Author: {0}  -  Modified: {1}",                 cd->Author, cd->ModifyDate);             Console::WriteLine(S"    Description: {0}", cd->Description);         }     }     else         Console::WriteLine(S"    No changes"); } void DisplayAttributes(MemberInfo *info) {     DisplayDescription(Attribute::GetCustomAttribute(info,                                  __typeof(DescriptionAttribute)));     DisplayHistory(info->GetCustomAttributes(__typeof(HistoryAttribute), true)); } void PrintClassInfo(Type *type) {     Console::WriteLine(S"Class: {0}", type->ToString());     DisplayAttributes(type);     ConstructorInfo *constructors[] = type->GetConstructors();     for (Int32 i = 0; i < constructors->Length; i++)     {         Console::WriteLine(S"Constructor: {0}", constructors[i]->ToString());         DisplayAttributes(constructors[i]);     }     MethodInfo *methods[] = type->GetMethods(static_cast<BindingFlags>       (BindingFlags::Public|BindingFlags::Instance|BindingFlags::DeclaredOnly));     for (Int32 i = 0; i < methods->Length; i++)     {         Console::WriteLine(S"Method: {0}", methods[i]->ToString());         DisplayAttributes(methods[i]);     }     PropertyInfo *properties[] = type->GetProperties(static_cast<BindingFlags>       (BindingFlags::Public|BindingFlags::Instance BindingFlags::DeclaredOnly));     for (Int32 i = 0; i < properties->Length; i++)     {         Console::WriteLine(S"Property: {0}", properties[i]->ToString());         DisplayAttributes(properties[i]);     } } Int32 main(Int32 argc, SByte __nogc *argv[]) {     try     {         Assembly *assembly = Assembly::LoadFrom(new String(argv[1]));         Type *types[] = assembly->GetTypes();         for (Int32 i = 0; i < types->Length; i++)         {              PrintClassInfo(types[i]);              Console::WriteLine();         }     }     catch(System::IO::FileNotFoundException*)     {         Console::WriteLine(S"Can't find assembly: {0}\n", new String(argv[1]));     }     return 0; } 
end example

One thing that this example has that the previous reflection example doesn't is the use of the BindingFlags enumeration. The BindingFlags enum specifies the way in which the search for members and types within an assembly is managed by reflection. In the preceding example I used the following flags:

 BindingFlags::Public | BindingFlags::Instance | BindingFlags::DeclaredOnly 

This combination of flags specified that only public instance members that have only been declared at the current level (in other words, not inherited) will be considered in the search.

Also notice that even though the DisplayAttributes() method is called with a parameter of type Type, ConstructorInfo, MethodInfo, or PropertyInfo, it is declared using a parameter of type MemberInfo. The reason this is possible is because all the previously mentioned classes inherit from the MemberInfo class.

Figure 17-3 shows DocumentationWriter.exe in action. The dates in Figure 17-3 are based on when I compiled the assembly and most likely will differ from your results.

click to expand
Figure 17-3: The DocumentationWriter program in action




Managed C++ and. NET Development
Managed C++ and .NET Development: Visual Studio .NET 2003 Edition
ISBN: 1590590333
EAN: 2147483647
Year: 2005
Pages: 169

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