Reflection Namespace

Team-Fly    

 
Visual Basic .NET Unleashed
By Paul Kimmel
Table of Contents
Chapter 6.  Reflection

Reflection Namespace

The Reflection namespace contains classes that support constructive access and management of assemblies. The Assembly class refers to an assembly, which includes the application and its metadata. There are classes representing modules, files, types, fields, global variables , attributes, parameter information, and much more.

Reflection is a big namespace that contains a lot of information. We will not be able to cover all of the capabilities of the Reflection namespace in this chapter, but it will act as a good starting point and provide you with a good foundation for further exploration of the Reflection capabilities.

Assemblies

Assemblies are represented in the CLR by the Assembly class. For practical purposes, an assembly is your application. In reality an assembly is your application and the metadata that describes the things that your application is made up of.

Note

Manifest information is information that makes an assembly self-describing . Refer to the later section "Manifest Resource Information."


The Assembly class is a root class in the Reflection namespace that is a starting point for obtaining manifest information and identifying and obtaining references to objects in an assembly, like modules, files, methods , and entry points. When you have these objects, you can dynamically invoke behaviors defined by elements in an assembly.

Assembly Class

The Assembly class implements getter methods that represent the objects in a module. The System.Reflection.GetAssembly method is a shared method that returns a reference to the assembly object containing the specified argument class.

Note

To use the capabilities of the Reflection namespace, add an Imports System. Reflection statement in the module that will use reflection, or add the System. Reflection namespace to the project imports.


By calling the Reflection.Assembly.GetAssembly method, passing the System.Type information to the method, you get a reference object to the assembly containing that object. For example, Reflection.Assembly.GetAssembly(Me.GetType) returns the assembly object containing the object represented by Me. If Me is a form's reference to self, a reference to the assembly containing that form will be returned. (An alternative to GetAssembly is GetCallingAssembly, which requires no parameters and returns a reference to the assembly containing the statement GetCallingAssembly.)

From the Assembly object you can obtain references to the modules, files, manifest information, entry point, and types defined in a specific assembly.

Assembly Members

Table 6.1 provides an overview of the members of the Assembly class.

Table 6.1. Members of the Assembly Class
Name Description
Shared Methods
CreateQualifiedName Creates the name of a type
GetAssembly Returns an assembly object containing the argument class
GetCallingAssembly Returns the assembly containing the method call
GetEntryAssembly Returns the first executable that was run
GetExecutingAssembly Returns the assembly containing the currently running code
Load Loads the named assembly
LoadFrom Loads the assembly by path
Instance Properties
CodeBase Returns the location of the assembly in URN format
EntryPoint Returns the starting point MethodInfo object for an assembly
Evidence Returns security policy information
FullName Returns the complete name of the assembly, including version and public key information
GlobalAssemblyCache Returns a Boolean indicating whether the assembly was loaded from the Global Assembly Cache (GAC)
Location Returns the URN of the loaded file
Instance Methods
CreateInstance Creates an instance of the argument type contained in the assembly
GetCustomAttributes Returns the assembly's custom attributes
GetExportedTypes Returns exported types defined in the assembly
GetFile Returns a FileStream object representing a specified file from the manifest, which includes a list of files in the assembly
GetFiles Returns an array of FileStream objects representing the files listed in an assembly's manifest
GetLoadedModules Returns the loaded modules that are in the assembly
GetManifestResourceInfo Returns information describing how a resource was persisted
GetManifestResourceNames Returns the names of the assembly's resources
GetManifestResourceStream Loads the manifest resource
Instance Methods
GetModule Returns the specified module
GetModules Gets all of an assembly's modules
GetName Returns the assembly's name
GetObjectData Returns serialization information needed to instantiate the assembly
GetReferencedAssemblies Returns all referenced assemblies
GetSatelliteAssembly Returns a satellite assembly containing culture-specific information
GetType Returns the type information for the specified type
GetTypes Returns the types defined in the assembly
IsDefined Returns a Boolean indicating if a custom attribute is defined
LoadModule Loads a module in the assembly
Instance Event
ModuleResolve Raised when the CLR cannot load a module

Refer to the sections that follow for individual code examples demonstrating some of the capabilities of the Assembly class.

Manifest Resource Information

Manifests contain data that describes how the elements of an assembly relate to each other. Manifests contain assembly name, culture, strong name, files in the assembly, type reference, and information indicating referenced assemblies. The assembly name, version number, culture information, and string information describe the assembly's identity, allowing assemblies with the same filename to coexist in the Global Assembly Cache (GAC).

Note

Culture information includes such things as language and country to support multinational application development.


You can change manifest information using attributes (see Chapter 12), including trademark, copyright, product, company, and version information.

The ManifestResourceInfo defines FileName, ResourceLocation, and ReferencedAssembly properties. The FileName property defines the resource containing the manifest information. ResourceLocation defines the location of the manifest resource file, and ReferencedAssembly returns a reference to the assembly containing this manifest.

Module Object

Assembly.GetModule returns a named module in an assembly. Module in this context does not refer to the Module construct. An assembly's module is equivalent to an application, like an executable or DLL module.

Tip

You can use keywords in code if you wrap the use of those keywords in square brackets. For example, Assembly is a reserved word. To declare a type as an Assembly type, you can prefix the Reflection namespace to the type or declare the type using brackets as follows : Dim A As [Assembly].

IntelliSense will automatically place the Assembly keyword in brackets when it is selected from a member list.


When you obtain a reference to a Module object, you can request information about Type objects, like classes and structures, and information contained in those types.

When we are referring to an assembly's module and reflection, we are referring to an application. When you have a specific application (module), you can obtain information about types, like the forms and classes in the module, and from those objects you can derive information about methods, fields, properties, events, and parameters. Listing 6.1 contains code that walks the methods and fields of a single type in a module.

Listing 6.1 Walking the fields and methods of a class in a module
  1:  Private Function GetAssembly() As Reflection.Assembly  2:  'Return Reflection.Assembly.GetAssembly(Me.GetType())  3:  Return Reflection.Assembly.GetCallingAssembly  4:  End Function  5:   6:  Private Sub InsertText(ByVal Text As String)  7:  TextBox1.Text &= Text & vbCrLf  8:  End Sub  9:   10:  Private Sub InsertModuleElements(ByVal AType As Type)  11:  Dim Enumerator As IEnumerator = _  12:  AType.GetMethods.GetEnumerator  13:   14:  InsertText("Methods:")  15:  While (Enumerator.MoveNext)  16:  InsertText(CType(Enumerator.Current, MethodInfo).Name)  17:  End While  18:   19:  InsertText("Fields:")  20:  Enumerator = AType.GetFields.GetEnumerator()  21:  While (Enumerator.MoveNext)  22:  InsertText(CType(Enumerator.Current, FieldInfo).Name)  23:  End While  24:   25:  End Sub  26:   27:   28:  Private Sub WalkModule()  29:  InsertText(GetAssembly.GetModules()(0).Name)  30:  InsertText(GetAssembly.GetTypes()(0).Name)  31:  InsertModuleElements(GetAssembly.GetTypes()(0))  32:  End Sub  33:   34:  Private Sub MenuItem10_Click(ByVal sender As System.Object, _  35:  ByVal e As System.EventArgs) Handles MenuItem10.Click  36:  WalkModule()  37:  End Sub 

Note

To run the sample code, open ReflectionDemo.sln and choose Demo, Assembly, GetModule. To run the code separately, create a new Windows application, include Imports System.Reflection, add a multiline TextBox, and define a new way to invoke WalkModule.


GetAssembly, defined on lines 1 through 4, is a query method that simplifies referring to an Assembly object. WalkModule uses GetAssembly to return the assembly containing the code in the listing.

The Assembly object is used to get the array of Modules on line 29 and the array of Types on line 30. The name from the 0th element of the modules is displayed and the name of the 0th element of the Types array is displayed.

InsertModuleElements on line 31 uses the Type at index 0 as the target type to walk. The sample code retrieves type Form1, which is a class, and passes that type to the InsertModuleElements procedure. The sample code gets Form1's methods and fields arrays on lines 1112 and 20 and uses the enumerator implemented by those arrays to walk each method and field defined in Form1.

You have seen verbose code that uses arrays and enumerators in earlier chapters, but I will repeat it here for clarity by elaborating on the code that examines each method. (The preceding code example is closer to the code we might like to deploy, but may be a little ambiguous.)

From Listing 6.1 we have the code on lines 11 through 17 that displays all of the methods in the type represented by AType.

  11:  Dim Enumerator As IEnumerator = _  12:  AType.GetMethods.GetEnumerator  13:   14:  InsertText("Methods:")  15:  While (Enumerator.MoveNext)  16:  InsertText(CType(Enumerator.Current, MethodInfo).Name)  17:  End While 

A verbose form of the same code follows.

 Dim MethodInfos() As MethodInfo MethodInfos = AType.GetMethods Dim Enumerator As IEnumerator Enumerator = MethodInfos.GetEnumerator() TextBox1.Text &= "Methods:" & vbCrLf Dim AMethodInfo As MethodInfo While (Enumerator.MoveNext)   AMethodInfo = CType(Enumerator.Current, MethodInfo)   TextBox1.Text &= AMethodInfo.Name & vbCrLf End While 

The revised verbose code performs singular operations on each line of code. Note, however, that it requires almost twice as many lines of code. The first revised line declares an array of MethodInfo types. The second line initializes that array by calling Type.GetMethods. The third and fourth lines declare and initialize an IEnumerator (introduced in Chapter 2 and explored further in Chapter 3). IEnumerator is an interface that defines two methods, MoveNext and Reset, and a property Current, providing you with a consistent interface for iterating elements of a System.Array. The fifth line adds some text to a TextBox indicating that the text that follows consists of method names. The sixth line declares a temporary variable used to store each MethodInfo object, and the loop appends the name of each methodAMethodInfo.Nameto the TextBox control.

Production systems should have concise rather than verbose code. A method named DisplayMethodNames that walks through the MethodInfo objects of a Type without creating several temporaries would yield clear and concise code.

File Object

The Assembly class contains GetFile and GetFiles methods. These methods return a FileStream object and an array of FileStream objects, respectively. The FileStream objects returned by the Assembly class are no different than any other FileStream object derived in any other manner; the methods and attributes are identical, only the state is different.

Listing 6.2 demonstrates how to load the bytes representing a file. Assembly.GetFile returns a FileStream representing the bytes of a module in an assembly.

Listing 6.2 Displaying some of the bytes in a module file as hexadecimal bytes using the ThreadPool and the FileStream returned by GetFiles()(0)
  1:  Private Contents As String  2:   3:  Private Sub WriteFileAsHex(ByVal state As Object)  4:   5:  Dim Stream As IO.FileStream = _  6:  GetAssembly.GetFiles()(0)  7:   8:  Dim B(Stream.Length) As Byte  9:  Stream.Read(B, 0, Stream.Length)  10:   11:  Dim I As Integer  12:   13:  SyncLock GetType(Form1)  14:  For I = 0 To B.Length() - 1  15:  If (I Mod 8 = 0 And I > 0) Then Contents &= " "  16:  If (I Mod 16 = 0 And I > 0) Then Contents &= vbCrLf  17:  Contents &= Hex(B(I)).PadLeft(2, "0")  18:   19:  ' Remove if you want the whole file!  20:  If (I > 50000) Then Exit For  21:  Next  22:  End SyncLock  23:   24:  Invoke(CType(AddressOf UpdateText, MethodInvoker))  25:   26:  End Sub  27:   28:  Private Sub UpdateText()  29:  TextBox1.Text = Contents  30:  StatusBar1.Text = "Loaded"  31:  End Sub  32:   33:  Private Sub MenuItem11_Click(ByVal sender As System.Object, _  34:  ByVal e As System.EventArgs) Handles MenuItem11.Click  35:   36:  StatusBar1.Text = "Loading File..."  37:  TextBox1.Clear()  38:   39:  Threading.ThreadPool.QueueUserWorkItem(_  40:  AddressOf WriteFileAsHex)  41:  End Sub 

Note

To avoid repeating a complete Windows application listing, the code in Listing 6.2 is an excerpt from ReflectionDemo.sln. To make the code a standalone example, create a new Windows application, add the Imports System. Reflection statement, copy GetAssembly from Listing 6.1, and replace the Windows Forms controls and events with the controls and events in your application.


When the user chooses Demo, Assembly, GetFile in ReflectionDemo.sln, the Click event handler on line 33 is invoked. The StatusBar.Text is updated, the TextBox is cleared, and something happens with the ThreadPool. We will come back to line 39 in a minute. Let's take a look at WriteFileAsHex.

Regardless of how WriteFileAsHex is invoked, what WriteFileAsHex does is request the FileStream represented by the Reflection.File object returned by indexing the 0th element of the array returned by Assembly.GetFiles. All of the bytes in the FileStream are read into the array of bytes B. Lines 14 through 21 convert all of the bytes to hexadecimal, formatting the bytes in two columns of eight bytes each.

Let's return to line 29 now. What is Threading.ThreadPool.QueueUserWorkItem? As you know, Visual Basic .NET supports free threading. There is a pool manager called the ThreadPool that contains some threads waiting around for you to give them some work to do. The ThreadPool manages the creation and allocation of threads; all you have to do is give them work by queuing a work item. Work items are represented by callback functions, also known as delegates.

There are several new topics here, including delegates, threads, thread pools, and how to safely use threads with controls. For now, you will have to take on faith that the code works correctly and safely. In summary, the ThreadPool is a pool of available threads, a delegate is a class representing a pointer to a function, and Windows Forms controls must be manipulated on the same thread as the one that owns them. The last point explains why the code uses the Invoke method on line 24 to update the TextBox.

Chapters 8 and 9 provide more information on delegates, and using the ThreadPool safely with Windows Forms controls is described in Chapter 15's section titled "Using the ThreadPool." The motivation for using threads in the example is to give you a first look at an advanced subject, multithreading, and to offload a potentially process-heavy task to a background thread, allowing the graphical user interface to remain responsive. Pushing big processes onto a different thread to allow our GUI to remain responsive reflects an actual reason we might want to use threads. More information about multithreaded applications can be found in Chapter 14, "Multithreaded Applications."

Location Property

The Assembly.Location object returns the complete path information of the assembly containing the manifest for the assembly represented by the Assembly object.

EntryPoint Property

Assembly.EntryPoint returns the MethodInfo object representing the method that is the entry point for an assembly. For example, a console application would return the shared sub main procedure where your console application begins executing. (Refer to the section on MethodInfo for more information on the type returned by the EntryPoint method.)

GlobalAssemblyCache Property

The Assembly.GlobalAssemblyCache property returns a Boolean value. If the property returns True, the assembly is registered in the GAC and is a public assembly; otherwise , the assembly is a private assembly.

Type

The System.Type object is an object that describes a type. When you invoke GetType or index one of the types in the array of types returned by GetTypes, you have an object that describes the interfaces, classes, structures, modules, and enumerations in an assembly. GetTypes returns nested types, too, like nested classes or structures.

The System.Type object is critical to obtaining and managing objects using Runtime Type Information (RTTI) and consequently central to reflection. Type objects allow you to query the object to determine whether the type represents a class or COM object, whether the type is nested, and the access modifiers that were applied to the type.

Reflection is used implicitly during certain types of operations. In VB6 you could declare a variable as a variant, assign a specific object to that variant, and call methods of the specific object without performing any type conversions. (Refer to the following example.)

 'VB6 Example Dim V As Variant Set V = New Collection V.Add "Foo" 

This type of code implicitly performs a call to QueryInterface. If the actual objectin this instance a Collectiondoes not support the interface, VB6 raises a runtime error. The preceding operation demonstrates late binding, or runtime binding. Late binding is supported in VB .NET by employing reflection. Unfortunately, this kind of lazy code defers a possible error until runtime. We want errors to manifest themselves as early as possible. For this reason, Chapter 2 suggested that Option Strict On be used to prevent late binding.

Refer to the help information for a complete reference of the Type members.

Binder

Binder objects are responsible for performing type conversions between arguments presented and the actual argument type needed.

Individual programming languages load and bind methods in a module individually. You can load assemblies and invoke methods on those assemblies using late binding through reflection. This is referred to as custom binding.

The most common scenario where this might be employed is by tools that allow users to explicitly invoke operations. Because the developer of such an application would not know in advance what a user may want to do, operations could not be associated with a visual metaphor in advance, such as a button or menu. For example, a user might pick from a selection of a thousand operations residing in any number of assemblies. Instead of trying to write static code for dynamic behavior, reflection and custom binding would allow you to load an assembly and invoke behaviors on that assembly as needed.

The subsections that follow demonstrate some of the ways in which assemblies can be loaded dynamically using reflection.

Loading Assemblies

Assemblies can be loaded at runtime without an application knowing what that assembly is in advance. Loading assemblies dynamically is probably not something you will do in the run-of-the-mill application, but just recently a developer from a local software company called me and asked me how to do this identical operation in a Delphi application. My response was "Oh, you're talking about reflection!" Unfortunately for that developer, the mechanics are not as thoroughly supported in Delphi as they are in the CLR. The call reminded me that dynamic type determination can be useful, especially for general tools builders. I am not sure, but it is a reasonable bet that reflection was designed for vendors building tools, and perhaps for Visual Studio .NET as a tool, too.

An assembly is an application containing metadata, which makes the assembly more than an application. Assemblies are self-contained files that look like applications on the outside but contain additional information like manifests and string name, public key data.

To load an assembly on the fly, you will need the name of an application containing assembly manifest information. From that application you can create an instance of the assembly returned by the Shared Assembly.Load function. And, of course, as we have been talking about, after you have loaded the assembly you can find out information about that assembly and invoke methods on types defined in the assembly. The end result is completely dynamic interaction with assemblies, or, applications, whichever you prefer.

The following fragment demonstrates loading an assembly:

 Dim AName As String = _   "C:\ Temp\ ReflectionEventDemo.exe"" Dim App As [Assembly] = [Assembly].LoadFrom(AName) 

The assembly MyLibrary.dll is a Class Library project.

The example demonstrates a hard-coded path, but you could just as easily return a dynamic path from the OpenFileDialog or a list of assemblies. The first statement declares a constant representing the filename, AName. The application name is used to initialize the assembly via the Assembly.LoadFrom method.

After this code fragment is executed, you have roughly the equivalent of LoadLibrary from the Win32 API days, or a late-bound COM object. (Remember this is a rough equivalence to LoadLibrary or late-bound COM objects.) Assembly is a keyword and a class; wrapping the word Assembly in brackets indicates that we are using the class name rather than the keyword associated with assembly attributes (see Chapter 12).

Now that we have a loaded assembly, we can use reflection and the Type.InvokeMethod operation to dynamically invoke behaviors on that assembly.

Invoking Members on Dynamic Assemblies

Earlier in the chapter, I mentioned that System.Type was essential to RTTI and reflection. One of those essential features is the ability to use a type rather than an instance to invoke members defined by a type.

Continuing with the example from the preceding section, we now have an assembly. We can use techniques from early sections of this chapter to discover what types are defined by that assembly and invoke behaviors defined by those types. For our example, we will use known information to keep the example simple (see Listing 6.3).

Listing 6.3 The listing for MyLibrary.dll and CustomBinding.vproj, both in CustomBinding.sln
  1:  ' MyLibrary.dll - Class1.vb  2:  Public Class Class1  3:   4:  Public Sub Echo(ByVal Message As String)  5:  MsgBox(Message)  6:  End Sub  7:   8:  End Class  9:   10:   11:  ' CustomBinding.exe - Form1.vb  12:   13:  Imports System.Reflection  14:   15:  Public Class Form1  16:  Inherits System.Windows.Forms.Form  17:   18:  [ Windows Form Designer generated code ]  19:   20:  Private Sub LoadAndInvoke()  21:  ' Modify to point to location of DLL on your PC  22:  Const FileName As String = "C:\ temp\ MyLibrary.dll"  23:   24:   25:  Dim AName As String = FileName  26:  27:  28:  Dim MyAssembly As [Assembly] = [Assembly].LoadFrom(AName)  29:   30:  Dim Arguments() As Object = {"Custom Binding Demo"}  31:   32:  Dim AType As Type = MyAssembly.GetTypes()(0)  33:  Dim O As Object = MyAssembly.CreateInstance(AType.FullName)  34:   35:  AType.InvokeMember("Echo", BindingFlags.Default Or _  36:  BindingFlags.InvokeMethod, _  37:  Nothing, O, Arguments)  38:  End Sub  39:   40:   41:  Private Sub Button1_Click(ByVal sender As System.Object, _  42:  ByVal e As System.EventArgs) Handles Button1.Click  43:  LoadAndInvoke()  44:  End Sub  45:  End Class 

The first section of Listing 6.3 implements the class library MyLibrary.dll as a class library that contains a single class with a single method, Echo. Beginning on line 10, the second section of the listing contains a simple application with a single form, Form1. Form1 contains a button. When the button is clicked (see lines 41 to 44), Form1.LoadAndInvoke is called. (Keep in mind that you could easily pick assemblies using dialogs or manually enter them by selecting an assembly from the GAC.)

LoadAndInvoke declares the path to the application containing the assembly manifest information and uses that object to LoadFrom the assembly with which the application is associated. (This is the code we covered in the prior section.) For the sake of brevity, line 30 picks up by creating an array of objects. The array of objects plays the role of arguments passed to the dynamically invoked method. On line 32 we arbitrarily retrieve the first type defined in the assembly. We know that MyLibrary.dll only defines one type, Class1. We could just as easily have displayed a list of types and let the user pick one. Line 33 uses type information contained in the type object to create an instance of the type with Assembly.CreateInstance. CreateInstance uses the default constructor that every type possesses to create an uninitialized instance of the type.

Finally, Type.InvokeMethod passes a string naming the method we want to call, Echo. The second argument indicates the BindingFlags; we want to invoke a method, so we pass the BindingFlags.Default or'd with BindingFlags.InvokeMethod, indicating the kind of invocation we are making. The third parameter is Binder; we will come back to the Binder parameter in a minute. The fourth parameter is the object that will be used to invoke the method, and the final parameter, Arguments, is the array of arguments to pass to the method. From the listing we know Echo takes a single string argument, which is what Arguments was initialized with.

The net result is that we invoked a method in an assembly that the user could have supplied, with a name that the user could have picked from a list of available names, and passed methods that we could have expressed to the user by displaying the ParameterInfo names for any particular method. As a result we have completely dynamic code.

Where Does Binder Fit In?

Binder is an abstract class that we must inherit from. Binder is defined with the MustInherit modifier. MustInherit means that we must declare an instance of a class that inherits from Binder and implement all of the methods labeled MustOverride.

What Binder is designed to do is describe a transposition between actual argument types to formal argument types based on the type of member invoked, binding arguments, and specific parameters. Chapter 7, "Creating Classes," covers inheritance in detail. Listing 6.4 implements a do-nothing binder subclass demonstrating the syntactical mechanics of inheriting from an abstract class and implementing virtual methods; that is, methods modified with MustOverride.

Listing 6.4 A do-nothing binder demonstrating the syntactical mechanics of inheriting an implementing MustOverride Binder method
  1:  Imports System.Globalization  2:   3:  Public Class MyBinder  4:  Inherits Binder  5:   6:  Public Overrides Function ChangeType(_  7:  ByVal value As Object, _  8:  ByVal type As Type, _  9:  ByVal culture As CultureInfo _  10:  ) As Object  11:   12:  Return value  13:   14:  End Function  15:   16:  Public Overrides Function SelectProperty(_  17:  ByVal bindingAttr As BindingFlags, _  18:  ByVal match() As PropertyInfo, _  19:  ByVal returnType As Type, _  20:  ByVal indexes() As Type, _  21:  ByVal modifiers() As ParameterModifier _  22:  ) As PropertyInfo  23:   24:  End Function  25:   26:  Public Overrides Function BindToMethod(_  27:  ByVal bindingAttr As BindingFlags, _  28:  ByVal match() As MethodBase, _  29:  ByRef args() As Object, _  30:  ByVal modifiers() As ParameterModifier, _  31:  ByVal culture As CultureInfo, _  32:  ByVal names() As String, _  33:  ByRef state As Object _  34:  ) As MethodBase  35:   36:  End Function  37:   38:  Public Overrides Function BindToField(_  39:  ByVal bindingAttr As BindingFlags, _  40:  ByVal match() As FieldInfo, _  41:  ByVal value As Object, _  42:  ByVal culture As CultureInfo _  43:  ) As FieldInfo  44:   45:  End Function  46:   47:   48:  Public Overrides Sub ReorderArgumentArray(_  49:  ByRef args() As Object, _  50:  ByVal state As Object _  51:  )  52:   53:  End Sub  54:   55:  Public Overrides Function SelectMethod(_  56:  ByVal bindingAttr As BindingFlags, _  57:  ByVal match() As MethodBase, _  58:  ByVal types() As Type, _  59:  ByVal modifiers() As ParameterModifier _  60:  ) As MethodBase  61:   62:  End Function  63:   64:  End Class 

As you can glean from the listing, the Binder methods use a relatively high number of arguments and many of those are array arguments. (System.Globalization contains the CultureInfo class used in the parameter for ChangeType.)

ChangeType

ChangeType is used to implement changing the type represented by the value parameter to the type argument.

SelectProperty

SelectProperty returns a property from the available properties based on the selection criteria arguments.

BindToMethod

BindToMethod picks a specific method based on the argument information provided.

BindToField

BindToField selects one of the available fields based on the argument values specified.

ReorderArgumentArray

ReorderArgumentArray allows you to reorganize the array of arguments passed to InvokeMethod (see Listing 6.4).

SelectMethod

SelectMethod provides the Binder with a chance to select a method based on the arguments passed.

When you invoke a method using historical technology, keep in mind that method invocation and parameter passing are constrained by the name, types, and order prescribed by the language that the application was implemented in. The .NET Framework replaces problems caused by language implementations and calling orders with classes in the Reflection namespace that allow the user to describe how method invocations are bound.

Listing 6.3 passes Nothing for the Binder object because we are working with languages that are core languages implemented by Microsoft. Custom binding objects will probably come into play when third-party vendors begin implementing tools in other languages that will be integrated into Visual Studio .NET.

Activator Class

The Activator class is defined in the System namespace. Activator contains methods for creating object instances locally or remotely or obtaining instances of existing objects.

When you call Type.CreateInstancesee Listing 6.3, line 33an instance of an Activator is created by the Type class to create the instance.

MethodInfo Class

MethodInfo objects are instances of classes that represent a method of a type. MethodInfo allows you to discover return types, modifiers, and attributes applied to the method, and a list of parameters used by the method.

As demonstrated throughout this chapter, discovery of method names and arguments allows the dynamic invocation of methods.

InsertModuleElements from Listing 6.1 demonstrates dynamic discovery of method names. Combine dynamic name discovery with dynamic parameter discovery, and you can invoke methods through the Type.InvokeMember method.

ParameterInfo Class

ParameterInfo objects represent the arguments associated with methods. ParameterInfo has properties that enable you to determine the name and type of the parameter, whether the parameter is optional, whether it has default values, and whether it is an input or output parameter.

FieldInfo Class

FieldInfo objects represent fields defined as members of a type. FieldInfo objects allow you to discover attributes, modifiers, names, and types at runtime.

PropertyInfo Class

By convention, fields contain private data represented by public property methods. It is intuitive that reflection supports all aspects of dynamic discovery and invocation, including property information.

EventInfo Class

The concept of the function pointer is new to Visual Basic .NET. In Chapters 8 and 9 you will learn that function pointers are types that point to the address of a function and those functions can be invoked via their addresses. Additionally, function pointers in .NET are wrapped in classes called Delegates.

Reflection supports the dynamic discovery of types, methods, fields, and events. This illustrates just how much Visual Basic has changed under .NET. In addition to having support for inheritance (refer to Chapter 7) and function pointers, the CLR supports the dynamic discovery of those function pointers, and subsequently, the dynamic assignment of event handlers to delegates.

Listing 6.5 demonstrates an application that loads a second executable assembly, creates an instance of the main form in that assembly, and assigns an event handler in the first application to an event in the second application.

Listing 6.5 Dynamic event handling using reflection
  1:  Private Sub AssignEvent()  2:   3:  Dim AName As String = "C:\ TEMP\ ReflectionEventDemo.exe"  4:   5:  Dim App As [Assembly] = [Assembly].LoadFrom(AName)  6:  Dim AType As Type = App.GetTypes()(0)  7:   8:  Dim AForm As Form = App.CreateInstance(AType.FullName)  9:  AForm.Show()  10:   11:  Dim Events() As EventInfo = AType.GetEvents  12:  Dim D As EventHandler = AddressOf Handler  13:   14:  Dim I As Integer  15:  For I = 0 To Events.Length - 1  16:  If (Events(I).Name = "Click") Then  17:  Events(I).AddEventHandler(AForm, D)  18:  Exit For  19:  End If  20:  Next  21:   22:  End Sub  23:   24:  Private Sub Handler(ByVal sender As System.Object, _  25:  ByVal e As System.EventArgs)  26:  MsgBox("Called from " & Application.ProductName)  27:  End Sub 

Caution

Listing 6.5 demonstrates dynamic event handler assignment across assembly boundaries using reflection. This is not the recommended way to assign event handlers. Read Chapters 8 and 9 for normal event-handling operations.


Listing 6.5 creates an assembly object containing a simple Windows application. Line 8 creates the assembly with the Assembly.Load method. Line 9 gets the first type in the assembly. We know there is only one, the main form; in a practical example we would want to perform some sanity checking on the Type returned as AType on line 9. Lines 14 through 23 get all of the events defined for the Form type, searching for the Click event. When the Click event is found, a handler defined in the first assembly is assigned to a delegate in the second assembly.

Note

To test the dynamic event assignment using reflection, you will need to choose Demo, Dynamic Event Reflection in the ReflectionDemo.exe to assign the event handler and click on the main form in the second application, ReflectionEventDemo.exe.


When you compile and run both of the assemblies in ReflectionDemo.sln, you will see that clicking on the second assemblyafter the event handler is assignedruns code in the first assembly. You can verify this operation by placing a breakpoint on line 29 from the listing.


Team-Fly    
Top
 


Visual BasicR. NET Unleashed
Visual BasicR. NET Unleashed
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 222

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