|
The final section of this chapter shows you how to use attributes at run time by inquiring about what attribute data an object contains. Querying attribute data is only one aspect of reflection, a feature of the .NET Framework 1.1 that lets you find out a lot of details about objects and the classes they belong to at run time. Not every aspect of reflection will be covered this chapter, but you’ll learn enough to be able to query attribute data.
Before I talk about reflection and how it relates to attributes, you need to know something about the Type class. System::Type is a class that represents type declarations. This means you can get a Type object to represent any object to which you have a reference, and you can then use that object to find out many details about the type. You can obtain Type objects to represent value types, arrays, classes, interfaces, and enumerations. It is the primary way to access metadata and the way in which you use reflection. Although the Type class is used mainly by developers writing language tools you might find it useful at times, such as when you want to access class attributes.
System::Type has a lot of members (over 40 properties and almost 50 methods). The following two tables list a selection of properties and methods from this class to show you the sort of information you can access through a Type object.
Property | Description |
---|---|
Assembly | Gets a reference to the assembly where the type is defined |
AssemblyQualifiedName | Gets the fully qualified name of the type, including the name of the assembly it was loaded from |
Attributes | Returns a TypeAttributes object representing the collection of attributes for this type |
BaseType | Returns a Type for the type from which this object directly inherits |
FullName | Returns the fully qualified name of the type, including namespace |
GUID | Returns the GUID associated with the type, if any |
IsAbstract | Returns true if the type is abstract |
IsArray | Returns true if the type is an array |
IsByRef | Returns true if the type is passed by reference |
IsClass | Returns true if the type is a reference type (and not an interface or value type) |
IsCOMObject | Returns true if the type is a COM object |
IsInterface | Returns true if the type is an interface |
IsValueType | Returns true if the type is a value type |
Module | Gets a reference to the module (the DLL) in which the type is defined |
Namespace | Gets the namespace of the type as a string |
UnderlyingSystemType | Gets a reference to the Type representing the CLR type underlying this language-specific type |
Method | Description |
GetConstructor, GetConstructors | Gets information about one or all of the constructors for the type |
GetEvent, GetEvents | Gets information about one or all of the events defined for the type |
GetField, GetFields | Gets information about one or all of the fields defined for the type |
GetInterface, GetInterfaces | Gets information about one or all of the interfaces implemented by the type |
GetInterfaceMap | Returns an InterfaceMapping showing how interface methods are mapped onto actual class methods |
GetMember, GetMembers | Gets information about one or all of the members of the type |
GetMethod, GetMethods | Gets information about one or all of the methods of the type |
GetProperty, GetProperties | Gets information about one or all of the properties defined by the type |
GetType | A static function that returns a Type object |
GetTypeFromCLSID, GetTypeFromProgID | Static functions that get a Type object representing a COM object |
InvokeMember | Invokes a member of the current type |
ToString | Returns the name of the type as a String |
You might think you need to use the Attributes property to find out about custom attribute properties, but Attributes allows access only to standard system attribute data.
You can use the Type class’s Attributes property to find out about the standard attribute settings for classes. This property returns TypeAttributes, which is a value type; it’s a set of flags describing which standard attributes are set for the type. This enumeration has nearly 30 members, and the following table shows you some of the common attributes that form part of TypeAttributes:
Member | Specifies that . . . |
---|---|
Abstract | The class is abstract |
AnsiClass | Strings are interpreted using ANSI character encoding |
AutoClass | The string encoding is automatically decided |
Class | The type is a class |
HasSecurity | The type has security information associated with it |
Import | The type has been imported from another assembly |
Interface | The type is an interface |
NotPublic | The type is not public |
Public | The type is public |
Sealed | The type cannot be extended by inheritance |
Serializable | The type can be serialized |
UnicodeClass | Strings are interpreted using Unicode character encoding |
You can find out whether a type has an attribute set by using the bitwise AND operator (&), as shown in the following code fragment:
if (tt->Attributes & TypeAttributes::Public) Console::WriteLine("Type is public");
If you want to check the whether the type is a class, a value type, or an
interface, you need to use the ClassSemanticsMask member:
if ((tt->Attributes & TypeAttributes::ClassSemanticsMask) == TypeAttributes::Class) Console::WriteLine(S"Type is a class");
Custom attribute data is accessed using the static GetCustomAttribute and GetCustomAttributes members of the Attribute class. As you’d expect, the GetCustomAttribute retrieves information about one attribute, whereas GetCustomAttributes returns you an array containing details of all the custom attributes for a type. This exercise will show you how to use the Type class and the GetCustomAttributes method to retrieve the attribute settings from the class you created in the previous exercise.
Continue using the TestAtts project that you started in the previous exercise. All classes dealing with reflection live in the System::Reflection namespace, so add the following using declaration to the others at the top of the source:
using namespace System::Reflection;
You need to create a Type object to use reflection to find out about custom attributes, so add this code to the start of the _tmain function:
int _tmain() { Console::WriteLine(S"Testing Attributes"); // Create an object and get its type TestAtts* ta = new TestAtts(3); Type* tt = ta->GetType(); return 0; }
You obtain a Type object using the GetType method that every .NET type inherits from System::Object.
You can see whether there are any custom attributes on a class by using the GetCustomAttributes method on the Type object, like this:
// See if there are any custom attributes on the class Object* patts[] = tt->GetCustomAttributes(true); int n = patts->Length; Console::WriteLine( S"Number of custom attributes on the class is {0}", __box(n));
We know that the class doesn’t have any custom attributes, so you’d expect a count of 0.
The attributes are actually on the class members, not on the class itself, so get a list of the class members and query them:
// Get info on the class members MemberInfo* pmi[] = tt->GetMembers(); int nMembers = pmi->Count; Console::WriteLine(S"Number of class members is {0}", __box(nMembers));
Calling GetMembers on the Type object returns an array of MemberInfo objects that describe the members. Running this code on the TestAtts class tells you that there are seven members.
Note | The seven members are the constructor, the private data value, the property get method, and four methods inherited from the Object base class (Equals, GetHashCode, GetType, and ToString). |
Loop over the list of class members, and get the custom attributes for each one:
for (int i=0; i<pmi->Count; i++) { Object* pMemberAtts[] = pmi[i]->GetCustomAttributes(true); if (pMemberAtts->Count > 0) { Console::WriteLine(S"Attributes for member {0}:", pmi[i]); for(int j=0; j<pMemberAtts->Count; j++) { Console::WriteLine(S" attribute is {0}", pMemberAtts[j]); } } }
The outer loop considers each member in turn and calls GetCustomAttributes on the MemberInfo object to get a list of attribute objects. If there are any attribute objects for this member, we print them out. As you’d expect, just passing an array element to WriteLine results in calling the appropriate ToString method, which normally prints out the name of the class.
There are several ways to figure out whether a member has the Documentation custom attribute, and the following code shows one of them. Modify the code for the inner loop in Step 4 so that it looks like this:
for (int j = 0; j < pMemberAtts->Count; j++) { Console::WriteLine(S" att is {0}", pMemberAtts[j]); DocumentationAttribute* pda = dynamic_cast<DocumentationAttribute*>(pMemberAtts[j]); if (pMemberAtts[j]->GetType()->Equals(pda->GetType())) { Console::WriteLine( S"The member has the Documentation attribute"); Console::WriteLine(S"Text is ’{0}’", pda->Text); } }
The loop first uses dynamic_cast to cast the current attribute as a DocumentationAttribute pointer. Then the Equals method compares the Type object of the current attribute against that of the DocumentationAttribute class to see whether they are the same. If they are, the loop retrieves the Text member of the current attribute and prints it.
Build and run the program. You should see console output similar to that shown here, with a listing of the attributes present on class members and a showing of documentation text values:
|