Reflection


Reflection is the ability to retrieve and examine at runtime the metadata that describes .NET Framework assemblies, modules, and types. You can then turn around and create dynamically, using the retrieved information, an instance of these types and then invoke its methods or access its properties or member variables.

The System::Reflection namespace, which the .NET Framework uses to support reflection, is made up of more than 40 classes. Most of these classes you will probably not use directly, if at all. Several of the more common classes you will use are listed in Table 17-1.

Table 17-1: Common System::Reflection Namespace Classes

CLASS NAME

DESCRIPTION

Assembly

Defines an assembly

AssemblyName

Provides access to all the parts of an assembly's name

ConstructorInfo

Provides access to the constructor's attributes and metadata

EventInfo

Provides access to the event's attributes and metadata

FieldInfo

Provides access to the field's attributes and metadata

MemberInfo

Provides access to the member's attributes and metadata

MethodInfo

Provides access to the method's attributes and metadata

Module

Defines a module

ParameterInfo

Provides access to the parameter's attributes and metadata

Pointer

Provides a wrapper class for a pointer

PropertyInfo

Provides access to the property's attributes and metadata

TypeDelegator

Provides a wrapper for an object and then delegates all methods to that object

Just to make things a little confusing, the key to .NET Framework reflection is the System::Type class which, as you can see, isn't even found within the Reflection namespace. My guess for its not being placed in the Reflection namespace is because it's used frequently and the designers of the Framework didn't want to force the import of the Reflection namespace.

Examining Objects

A key feature of reflection is the ability to examine metadata using the System::Type class. The basic idea is to get a Type reference of the class you want to examine and then use the Type class's members to get access to the metadata information about the type, such as the constructors, methods, fields, and properties.

Getting the Type Reference

In most cases, you will get the Type reference to the class by one of four methods:

  • Using the _typeof() operator

  • Calling the class's GetType() method

  • Calling the Type class's static GetType() method, passing it the name of the class to be examined as a String

  • Iterating through collection of all types within an assembly retrieved by the Assembly class's GetTypes() method

The first method is the easiest of the four ways to get a Type reference. With the _typeof() operator, you pass it the data type and it returns a Type reference to the class:

 System::Type *myClassRef = __typeof(MyClass); 

To use the second method you need to already have an instance of the managed object you want to examine, and with this instance you call its GetType() method. The key to the second method is the fact that all managed classes and value types inherit from the Object class and the Object class has a GetType() method. For example, here is how you would get the Type reference to the myClass class:

 __gc class myClass {     // members }; MyClass *myClass = new MyClass(); Type *myClassRef = myClass->GetType(); 

It is a little trickier if the type you are trying to get the Type reference from is a value type, because you have to __box the object before calling the GetType() method (a compile-time error points this out to you as well):

 __value struct MyStruct {      // members }; MyStruct myStruct; Type *myStructRef = __box(myStruct)->GetType(); 

The third method is kind of cool in that you pass the string equivalent of the type you want to reference to the Type class's static GetType() method. You might want to note that Type is an abstract class, so you can't create an instance of it but, as you can see here, you can still call its static methods:

 Type *myClassRef = Type::GetType(S"MyClass"); 

One thing all the preceding methods have in common is that you need to have something of the type you wanted to reference at runtime—either the data type and instance of the type, or the name of the type. The fourth method allows you to get a Type reference without any knowledge of the object beforehand. Instead, you retrieve it out of a collection of Types with an assembly:

 Assembly* assembly = Assembly::LoadFrom(S"MyAssembly.dll"); Type *types[] = assembly->GetTypes(); for (Int32 i = 0; i < types->Length; i++) {     Type *myTypeRef = types[i]; } 

Getting the Metadata

Getting the metadata out of a Type reference is the same no matter what method you use to attain the Type reference. The Type class contains numerous methods, many of which allow you to access metadata associated with the type. Table 17-2 lists of some of the more common methods available to you for retrieving metadata.

Table 17-2: Common Type Metadata Retrieval Methods

METHOD

DESCRIPTION

GetConstructor()

Gets a ConstructorInfo object for a specific constructor of the current Type

GetConstructors()

Gets a collection of ConstructorInfo objects for all the constructors for the current Type

GetEvent()

Gets an EventInfo object for a specific event declared or inherited from the current Type

GetEvents()

Gets a collection of EventInfo objects for all the events declared or inherited from the current Type

GetField()

Gets a FieldInfo object for a specific member variable from the current Type

GetFields()

Gets a collection of FieldInfo objects for all the member variables from the current Type

GetInterface()

Gets a Type object for a specific interface implemented or inherited from the current Type

GetInterfaces()

Gets a collection of Type objects for all the interfaces implemented or inherited from the current Type

GetMember()

Gets a MemberInfo object for a specific member from the current Type

GetMembers()

Gets a collection of MemberInfo objects for all the members from the current Type

GetMethod()

Gets a MethodInfo object for a specific member method from the current Type

GetMethods()

Gets a collection of MethodInfo objects for all the member methods from the current Type

GetProperty()

Gets a PropertyInfo object for a specific property from the current Type

GetProperties()

Gets a collection of PropertyInfo objects for all the properties from the current Type

Along with the "Get" methods, the Type class also has a number of "Is" properties (see Table 17-3), which you use to see if the current type "is" something.

Table 17-3: Common "Is" Properties

"IS" PROPERTY

DESCRIPTION

IsAbstract

Is a Boolean that represents whether the Type is abstract

IsArray

Is a Boolean that represents whether the Type is a managed array

IsClass

Is a Boolean that represents whether the Type is a managed (_gc) class

IsEnum

Is a Boolean that represents whether the Type is a __value enumeration

IsImport

Is a Boolean that represents whether the Type is an import

IsInterface

Is a Boolean that represents whether the Type is a .NET interface

IsNotPublic

Is a Boolean that represents whether the Type is not public

IsPointer

Is a Boolean that represents whether the Type is a pointer

IsPrimitive

Is a Boolean that represents whether the Type is a .NET primitive (Int32, Single, Char, and so on)

IsPublic

Is a Boolean that represents whether the Type is public

IsSealed

Is a Boolean that represents whether the Type is sealed

IsSerializable

Is a Boolean that represents whether the Type is serializable

IsValueType

Is a Boolean that represents whether the Type is a value type

Listing 17-1 shows a how to build a handy little tool that displays the member methods, properties, and variables of the classes found in the six most commonly referenced assemblies in the .NET Framework using reflection.

Listing 17-1: Referencing the Class Members of the .NET Framework

start example
 namespace Reflecting {      //...Standard Usings      using namespace System::Reflection;      public __gc class Form1 : public System::Windows::Forms::Form      {           //...Auto generated GUI Interface code      private: Type *types[];      private: static String *assemblies[] = {          S"System",          S"System.Drawing",          S"System.Xml",          S"System.Windows.Forms",          S"System.Data",          S"mscorlib"      };      private:          System::Void Form1_Load(System::Object *sender, System::EventArgs *e)          {               for (Int32 i = 0; i < assemblies->Length; i++)               {                    cbAssemblies->Items->Add(Path::GetFileName(assemblies[i]));               }               cbAssemblies->SelectedIndex = 0;          }      private:          System::Void cbAssemblies_SelectedIndexChanged(System::Object * sender,                                                            System::EventArgs * e)          {               Assembly* assembly = Assembly::LoadWithPartialName(                                      assemblies[cbAssemblies->SelectedIndex]);               types = assembly->GetTypes();               cbDataTypes->Items->Clear();               for (Int32 i = 0; i < types->Length; i++)               {                   cbDataTypes->Items->Add(types[i]->ToString());               }                   cbDataTypes->SelectedIndex = 0;          }      private:          System::Void cbDataTypes_SelectedIndexChanged(System::Object * sender,                                                             System::EventArgs * e)          {              Type *type = types[cbDataTypes->SelectedIndex];              MemberInfo *methods[] = type->GetMethods();              lbMethods->Items->Clear();              for (Int32 i = 0; i < methods->Length; i++)              {                  lbMethods->Items->Add(methods[i]->ToString());              }              PropertiesInfo *properties[] = type->GetProperties();              lbProperties->Items->Clear();              for (Int32 i = 0; i < properties->Length; i++)              {                  lbProperties->Items->Add(properties[i]->ToString());              }              MemberInfo *variables[] = type->GetFields();              lbVariables->Items->Clear();              for (Int32 i = 0; i < variables->Length; i++)              {                  lbVariables->Items->Add(variables[i]->ToString());              }          }      }; } 
end example

Note

To save space and because it isn't directly relevant, all the code examples in this chapter don't include the autogenerated Windows Form GUI code. (See Chapters 9 and 10 for more information on Windows Form development.)

As you can see from the code in the preceding example, reflection can be fairly easy to work with. Simply "Get" the metadata needed and then loop through the metadata. Admittedly, the example is not the most elaborate, but it still shows the potential power it has in making the metadata information within an assembly available. It also helps point out why the header include file is no longer needed for library assemblies, as I explained in Chapter 4.

Most of the preceding code is simply to load the appropriate GUI controls, but one thing new in the preceding example that hasn't been covered before is the use of the System::Reflection::Assembly class. The Assembly class is a core building block of all .NET Framework applications, though normally, even as a .NET developer, you seldom have to know of its existence.

When it comes to reflection, the Assembly class contains the starting point for retrieving any public metadata information you want about the current active assembly or one that you load using one of the many different loading methods. The only reason I see that there are multiple load methods (each has multiple overload) is due to the duplicated method signature required to support the myriad ways available to load an assembly. Essentially, all load methods do the same thing—load the assembly—with the only differences relating to the amount of information known about the assembly being loaded and the source of the assembly.

The LoadWithPartialName() method requires the least amount of information—simply the name of an assembly. It does not care about version, culture, and so on. It is also the method that the .NET Framework frowns upon using for that exact reason. But in the case of this example it works just fine.

Figure 17-1 shows Reflecting.exe in action. As you can see, it's made up of two ComboBoxes and three ListBoxes. The first ComboBox provides a way of selecting the assembly, and the second allows you to select the type. The results of these two selections are the methods, properties, and variables displayed in the ListBoxes.

click to expand
Figure 17-1: The Reflecting program in action

Dynamically Invoking or Late-Binding Objects

Reflection provides you with the rather powerful feature known as late binding. Late binding is the ability to determine which method is dynamically invoked at runtime as opposed to compile time.

A cool thing about reflection is that once you have a reference to the method you want to invoke (which I showed how to do previously), it is not a large step to execute that method in a dynamic fashion. In fact, all you have to do is invoke the method using the (you guessed it) MethodInfo::Invoke() method.

The trickiest part of invoking methods using reflection is realizing that there are two types of methods: static and instance. Static methods are the easiest to handle, as you don't need to create an instance of the method's class to invoke it. Simply find the Method reference type and then use the Invoke() method:

 MethodInfo *method = type->GetMethod(); method->Invoke(0, 0); 

Notice that in the preceding example the Invoke() method has two parameters. The first is the instance of the class for which you are invoking the method. The second is an array of parameters that will be passed to the method. As you can now tell, the preceding example is not only a static method. It also takes no parameters.

If the method you want to invoke is an instance method, it is not quite as easy because you need to create an instance of the type for that method. The .NET Framework provides you help in the way of the System::Activator class, which contains the static CreateInstance() method to create objects:

 Type *type = assembly->GetType("MyType"); Object *typeInstance = Activator::CreateInstance(type); 

Now that you have an instance of the method class, all you have to do is pass it as the first parameter:

 method->Invoke(typeInstance, 0); 

To pass parameters to the Invoke() method, simply create an array of them and assign the array to the second parameter:

 Object *args[] = new Object*[2]; args[0] = parameterOne; args[1] = parameterTwo; 

That's really all there is to late binding.

Listing 17-2 shows how to execute both a static and an instance method using reflection. The first thing the example does is create an array using reflection of all the static color properties of the Color structure. It then displays the color as the background of a label by invoking the property's getter method. Next, the example dynamically invokes a method from one of two different classes to display the color name in the label. (There are much easier ways to do this without reflection, obviously.)

Listing 17-2: Using Reflection to Change the Properties of a Label

start example
 namespace Invoking {     //...Standard Usings     using namespace System::Reflection;     public __gc class Form1 : public System::Windows::Forms::Form     {         //...Auto generated GUI Interface code     private: PropertyInfo *colors[];     private:         System::Void Form1_Load(System::Object * sender, System::EventArgs * e)         {             // Get the reference to the Color type             Type* colorType = __typeof(Color);             // Get all the Color type's properties             colors = colorType->GetProperties();             for (Int32 i = 0; i < colors->Length; i++)             {                 // only display the static color properties                 if (colors[i]->ToString()->IndexOf(S"System.Drawing.Color") >=0)                     cbColor->Items->Add(colors[i]->ToString());             }             cbColor->SelectedIndex = 0;         }     private:         System::Void cbColor_SelectedIndexChanged(System::Object * sender,                                                       System::EventArgs * e)         {             static Boolean alternateWrite = true;             PropertyInfo *property = colors[cbColor->SelectedIndex];             // Get the property's getter method             MethodInfo *PropMethod = property->GetGetMethod();             // Invoke the static method             lbColor->BackColor = *dynamic_cast<Color*>(PropMethod->Invoke(0,0));             // Load the assembly Invoking.             // You could also have used the currently executing assembly             Assembly *assembly = Assembly::Load("Invoking");             // get the type based on the Boolean status of alternateWrite             Type *type;             if (alternateWrite)                 type = assembly->GetType("Invoking.Writer1");             else                 type = assembly->GetType("Invoking.Writre2");             alternateWrite = !alternateWrite;             // Get the aColor Method             MethodInfo *ColorMethod = type->GetMethod("aColor");             // Create an instance of the type             Object *writerInst = Activator::CreateInstance(type);             // Create the parameter - the color             Object *args[] = new Object*[1];             args[0] = PropMethod->Invoke(0,0);             // Invoke the instance method             lbColor->Text =                 dynamic_cast<String*>(ColorMethod->Invoke(writerInst, args));         }     };     __gc class Writer1     {     public:         String *aColor(Color *col)         {             return String::Format("[Writer 1] {0}", col->ToString());         }     };     __gc class Writer2     {     public:         String *aColor(Color *col)         {             return String::Format("[Writer 2] {0}", col->ToString());         }     }; } 
end example

Note

The GetType() method uses C# syntax when looking at the type within the assembly. Therefore, it uses a period (.) in place of a double colon (::).

As you can see from the preceding example, there is quite a bit of overhead involved in reflection and late binding, so you should use these techniques sparingly.

Figure 17-2 shows Invoking.exe in action. Pay attention to the text that prefixes the color displayed in the label as it alternates from "[Writer 1]" to "[Writer 2]".

click to expand
Figure 17-2: The Invoking 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