Loading Assemblies

One of the capabilities I have been referring to is the ability to load an assembly at runtime, then discover and invoke operations on that assembly. One of the compelling applications of this technology is the new thin client programming. Thin client is coming to mean automatic deployment of updates of Windows applications over the Internet. (Refer to Chapter 10 for more information on this subject.)

Although you could load any assembly and randomly search for members, in practice you will probably have an idea of what you are looking for. Either way, the code for loading an assembly and invoking members is pretty much the same. You will need to load the assembly, create an instance of a type in that assembly, and bind and invoke a member. I will demonstrate each of these subjects in this section.

Loading an Assembly

The Assembly class is defined in System.Reflection . You can use the shared Assembly.Load and Assembly.LoadFrom methods to dynamically load an assembly. The Assembly.Load method requires an AssemblyName parameter, which is comprised of version, culture, and a strong name or public key. It is easier to use Assembly.LoadFrom and simply provide the assembly name.

The following statement demonstrates how to load an assembly. (Make sure you add an Imports System.Reflection statement in the module containing code referring to the Assembly class.)

 Dim MyAssembly As [Assembly] = _   [Assembly].LoadFrom(Application.ExecutablePath) 

TIP

When you use keywords that are also variables or types, enclose that keyword variable in brackets, as demonstrated above by [Assembly] .

The Assembly.LoadFrom method might throw an ArgumentNullException , FileNotFoundException , BadImageException , SecurityException , or FileLoadException . (Refer to the Understanding Reflection and Security section later in this chapter for more information on security exceptions.) For example, you might want to catch a FileNotFoundException and allow the user to browse for the assembly. Here is an example of code that catches a FileNotFoundException and displays the contents of the exception's message.

 Try   Dim MyAssembly As [Assembly] = _     [Assembly].LoadFrom(Application.ExecutablePath) Catch X As System.IO.FileNotFoundException   MsgBox(X.Message, MsgBoxStyle.Exclamation) End Try 

The Try block attempts to perform the action. The Catch block performs the error handler behavior. In this instance we catch the specific exception object and display the exception message. (I use X by convention to represent the exception object. You can use anything you want, but exception-handling blocks are supposed to be short, and X is sufficient.)

After the assembly is loaded, you will want to retrieve some information from the assembly or perform some operation defined by one of the types in the assembly. Our example demonstrates an assembly that loads itself. In the next subsection we'll create a new Hello World assembly and invoke an operation in that assembly.

Invoking Reflected Methods

Having loaded an assembly, we want to interact with the types and the members of those types. I have contrived a simple HelloWorld.dll assembly that has a class with one shared method. Listing 4.3 demonstrates how to load that assembly and invoke a shared method.

Listing 4.3 Loading an Assembly and Invoking a Shared Method
 1:   Const Path As String = _ 2:      "C:\Books\Addison-Wesley\Visual Basic .NET Power Coding\" + _ 3:      "SOURCE\CHAPTER 4\HelloWorld\bin\HelloWorld.dll" 4: 5:    Dim HelloWorld As [Assembly] = _ 6:      [Assembly].LoadFrom(Path) 7: 8:    Dim ReflectedClass As Type = _ 9:      HelloWorld.GetType("HelloWorld.ReflectedClass") 10:   ReflectedClass.InvokeMember("HelloWorld", _ 11:     BindingFlags.InvokeMethod Or BindingFlags.Public _ 12:     Or BindingFlags.Static, _ 13:     Nothing, Nothing, Nothing) 

Lines 1, 2, and 3 contain a really long file path. I left the long file path in place to introduce you to a convenient trick. You can drag a project from the Solution Explorer to the Code Editor to correctly and easily copy long file paths. (When you experiment with the code in Listing 4.3, instead of recreating the file path on lines 1 through 3, simply copy the sample code to a C:\temp directory or something short and similar.) Lines 5 and 6 load the assembly from the file path constant.

Lines 8 and 9 retrieve a type from the loaded assembly that we know exists because we wrote it. If you wanted to get all the types defined in the assembly, you could call GetTypes (note the plural) on line 9 and assign the result to an array of types.

Lines 10 through 13 might be hardest to follow. This code is simply calling the method ReflectedClass.HelloWorld . The first parameter is the name of the method we are invoking. The second argument refers to the binding flags that tell Reflection what to look for. According to the Or 'd BindingFlags enumerated values, we are invoking a public, static method. ( Static is a synonym for Shared in this context. The C# language uses Static instead of Shared ; Microsoft had to choose one word, and Static apparently won.)

The last three arguments represent a binder, an instance to invoke the member on, and the arguments to pass. In this simple example, we don't need a binder (we'll return to the Binder class in a minute), and the HelloWorld method is static, which means we do not need an instance of the class to invoke the method on. Finally, HelloWorld as I defined it has no arguments, so passing Nothing for the array of arguments is satisfactory.

The end result is that the statement started on line 10 invokes the ReflectedClass.HelloWorld method. More commonly, we will be interacting with members that are not shared. It will be helpful to have an object, use a binder, and pass parameters. Next let's take a look at how we create and use an instance and pass parameters. After that, we'll review the Binder class.

Creating an Instance from a Type Object

Most applications will not conveniently have all static methods with no parameters. A more realistic situation is that we will need to invoke an instance member and pass arguments to methods. To accomplish this we need to create an instance of the dynamically Reflected objects. There are two ways we can accomplish this: (1) we can use the Activator class to create an instance, or (2) we can invoke a constructor method. Both examples are demonstrated next.

Creating an Instance with the Activator Class

The Activator class supports creating .NET assemblies and COM objects dynamically. The Activator class contains four shared methods ” CreateComInstanceFrom , CreateInstance , CreateInstanceFrom , and GetObject ”that will load COM objects or assemblies and create instances of the named typed. For example, GetObject allows you to create an instance of a remote object or an XML Web Service.

The code in Listing 4.4 demonstrates how to create an instance of ReflectedClass , which was introduced in Listing 4.3.

Listing 4.4 Using the Activator.CreateInstance Method to Dynamically Create a Reflected Type
 Dim HelloWorld As [Assembly] = [Assembly].LoadFrom(Path)   Dim ReflectedClass As Type = HelloWorld.GetType( _     "HelloWorld.ReflectedClass") Dim Instance As Object = Activator.CreateInstance(ReflectedClass) 

The first statement uses Path as defined in Listing 4.3. (We are still working with the same HelloWorld assembly.) The second statement uses the GetType method to get a type from the dynamically loaded assembly. (We could have obtained the name of the type in a variety of ways, including listing all types with GetTypes and allowing the user to select a type.) The third statement uses the Activator.CreateInstance method, passing the type of the object, to create an instance of the Reflected type. After the third statement executes, the result is an instance of the ReflectedClass defined in the HelloWorld namespace.

If we set Option Strict Off , we could invoke a method on Instance . For example, we could write Instance.ShowMessage("Hello World!") , and late binding and implicit Reflection would take care of the invocation for us. A reasonable programmer might elect to do this and could argue that this is a good example of when you may not want to use Option Strict On .

We'll come back to binding a method with parameters in a minute. For now let's look at the second way to create an instance using Reflection.

Dynamically Invoking a Constructor

The System.Reflection namespace contains classes named membertype Info . For example, ConstructorInfo represents a constructor. There are classes representing constructors, methods, fields, properties, events, members (in general), and parameters. Following convention, these classes are named ConstructorInfo , MethodInfo , FieldInfo , PropertyInfo , EventInfo , MemberInfo , and ParameterInfo , respectively. Each of these classes is an object-oriented representation of the type information for an element. The admission pass is the System.Type class. If you request an instance of the type information for any type, you can obtain the type information for any element of that type. Of course, you are not guaranteed to be able to get any information about assemblies; security plays a role, too. (See the Understanding Reflection and Security section later in this chapter for more information.)

NOTE

Attribute information is handled a little bit differently. Use the GetCustomAttribute and GetCustomAttributes methods of any System.Type object to request attribute information.

We can use this knowledge to obtain the type information for constructors. If you request a ConstructorInfo object from a type, you can use that constructor to create an instance of the class. Listing 4.5 demonstrates how to request a ConstructorInfo object and create an instance of ReflectedClass .

Listing 4.5 Obtaining a ConstructorInfo Object and Creating an Instance of a Reflected Type
 Dim HelloWorld As [Assembly] = [Assembly].LoadFrom(Path) Dim ReflectedClass As Type = HelloWorld.GetType("HelloWorld.ReflectedClass") Dim Constructor As ConstructorInfo = _   ReflectedClass.GetConstructor(New System.Type() {}) Dim Instance As Object = Constructor.Invoke(New Object() {}) Instance.ShowMessage("ConstructorInfo") 

The first two statements you have already seen. The third statement uses the type object, ReflectedClass , and requests a single ConstructorInfo type record. The argument New System.Type() {} is used to tell the GetConstructor method which constructor to retrieve. The array of types is used to match parameters. For example, if the constructor you wanted to obtain required a string argument, you would pass the type information for a string (see the upcoming Passing Method Arguments section).

The statement containing Constructor.Invoke (in Listing 4.5) passes an array of matching parameters to the actual constructor. Because we passed an empty array of System.Type objects, we need to invoke the constructor with an empty array of Object parameters.

Listing 4.5 invokes the ShowMessage method. From the listing it is apparent that we are using the Option Strict Off , late bound form of the method invocation.

NOTE

Hopefully these examples demonstrate some of the vast power inherent in object-oriented systems with a coherent framework. Without a common type ” Object ”and type objects, it would be very difficult or impossible to implement and support capabilities like Reflection.

Any type may have zero or more of any of the elements described. For instance, constructors can be overloaded; hence a class may have multiple constructors. To reflect reality, the System.Type class also has plural forms of the get methods. The plural form ”for example, GetConstructors() ”returns an array of the type information objects. You can use the plural form of the Get membertype method to retrieve all the elements of a specific kind. For example, all the properties can be obtained and displayed as demonstrated in Listing 4.6.

Listing 4.6 Iterating All the Properties of a Type
 Dim FormType As Type = Me.GetType Dim Properties As PropertyInfo() = FormType.GetProperties() ListBox1.Items.Clear() Dim [Property] As PropertyInfo For Each [Property] In Properties   ListBox1.Items.Add([Property]) Next 

The first statement gets the type information for the containing object. (The example code is in LoadAssembly.sln , and Me represents a form.) The second statement gets all the PropertyInfo records. The third statement clears a ListBox ; we'll use the ListBox to display the property information. The For Each loop employs an enumerator behind the scenes and adds every PropertyInfo representation to the ListBox .

A couple of implied things are going on in Listing 4.6. The variable [Property] uses brackets because Property is a reserved keyword. In the statement where we are adding items to the ListBox , the [Property] argument resolves to [Property].ToString() . We can add objects to a ListBox , but a string representation of those objects is displayed in the ListBox . (This is an advanced book. We have to have a few enigmas for you to figure out.)

Passing Method Arguments

Realistically you are likely to invoke nonstatic methods. You are also likely to have to invoke methods that have parameters. Listing 4.5 demonstrated how to invoke a constructor with null arguments. If we have a constructor that has some arguments, all we need to do is modify the array of types to indicate which constructor to request and then invoke that constructor with matching argument types.

To demonstrate parameterized method invocation, we will make some adjustments to our example. A constructor with a string argument might appear as follows :

 Public Sub New(ByVal Message As String) 

The call to the GetConstructor method for the preceding constructor would be

 GetConstructor(New System.Type(){GetType(String)}) 

We are indicating that GetConstructor should return the Sub New constructor that has a single string parameter. To invoke the constructor with a string argument, we need to pass a string to that constructor. Here is the invocation call.

 Dim Instance As Object = Constructor.Invoke( _   New Object() {"It's all about the crowbar love!"}) 

NOTE

In case you're curious , "crowbar love" is a reference to the Half-Life video game. Half-Life is a very graphic, multiplayer game, and one of the weapons is a crowbar. On my birthday, a good friend of mine, Eric Cotter, brought me a crowbar in the middle of a postwork game of Half-Life.

The same technique for passing arguments to constructors is used to pass arguments to methods.



Visual Basic. NET Power Coding
Visual Basic(R) .NET Power Coding
ISBN: 0672324075
EAN: 2147483647
Year: 2005
Pages: 215
Authors: Paul Kimmel

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