As mentioned in Chapter 2, you can explore the internals of a given assembly using a process called reflection. You can find out what assemblies are loaded into your current application domain. You can discover what types reside in each assembly, and for any given type, the methods and properties exposed by the type. You can even execute a method or change a property value via reflection, even though you might not know the name of the method or property at compile time.
In this section, you'll see the basic code required for each of these operations. The code uses classes in the System.Reflection namespace, most notably the Assembly class, and each example assumes that the code module has an Imports statement to import System.Reflection.
Major classes needed to use reflection capabilities include the following:
After this section, you will also see an additional capability provided through reflection: dynamic loading. You'll see how to gain a reference to an assembly on the fly and generate an instance of a type within the assembly.
Almost all work with reflection will require you to work with the Assembly class. An instance of this class is associated with a .NET assembly.
There are several ways to get a reference to an instance of an Assembly class. Several shared methods of the Assembly class can return such an instance. The ones most commonly used are:
Here is a code example that gets an assembly reference using each of the first three of these methods. The fourth method is covered in the section on dynamic loading later in the chapter.
Dim Assembly1 As [Assembly] Assembly1 = [Assembly].GetAssembly(GetType(System.Boolean)) ’ This would return a reference to mscorlib Dim Assembly2 As [Assembly] Assembly2 = [Assembly].GetExecutingAssembly ’ This would return a reference to the assembly ’ containing this code. Dim Assembly3 As [Assembly] Dim sFileName As String sFileName = "C:DevMyProjectinReleaseMyLibrary.dll" Assembly3 = [Assembly].LoadFile(sFileName)
You can also get a reference to an assembly by first getting a list of the assemblies loaded into an application and then choosing an assembly from that list.
The application domain is the context for your current running application. You can work with an application domain using the AppDomain class in the System namespace. AppDomain has a shared property called CurrentDomain that will return the application domain in which you are currently running.
An application domain instance has a GetAssemblies method to obtain the assemblies currently loaded in the application domain. GetAssemblies returns an array of type Assembly.
Putting these capabilities together, you can print out the long name of each assembly in the current application domain using the following code:
Dim LoadedAssemblies As Assembly() 'Get the list of loaded assemblies from the current AppDomain. LoadedAssemblies = AppDomain.CurrentDomain.GetAssemblies() For Each LoadedAssembly In LoadedAssemblies ' There are many operations available on ' each assembly. This code simply lists the ' assembly's full name. Console.WriteLine(LoadedAssembly.FullName) Next
Chapter 2 discussed types in .NET. To recap, a type is a class, structure, or native value type such as a Double or Boolean.
A type is represented during reflection by an instance of the Type class. As Chapter 2 explained, you can get a reference to a type by using the GetType method of the type. However, you can also get a reference to a type via a method on an instance of the Assembly class that is associated with the assembly containing the type.
The GetTypes method of an Assembly class instance returns an array containing all the types in the assembly. You can also get a reference to a single type in an assembly with the GetType method, which takes a string with the fully qualified namespace pathname of the type.
For example, the following code will print out the names of all the types in the assembly containing the currently executing code:
Dim CurrentAssembly As [Assembly] CurrentAssembly = [Assembly].GetExecutingAssembly For Each IndividualType In CurrentAssembly.GetTypes Console.WriteLine(IndividualType.Name) Next
Reflection also allows you to explore a type and discover the members (properties and methods) of the type. The GetProperties method of a type will return an array of property descriptor objects, and the GetMethods method will return an array of method descriptors in the form of MethodInfo instances. The more general GetMembers method will return all the members of a Type, including properties, methods, events, and so forth. The following code, when placed inside a class, will print out all the properties, events, and public methods for the class:
For Each Member In Me.GetType.GetMembers Console.WriteLine(Member.Name) Next For Each IndividualProperty In Me.GetType.GetProperties Console.WriteLine(IndividualProperty.Name) Next
There is some redundancy between these two methods. At a binary level, properties are actually pairs of get and set methods. That means you will see the get and set methods for a type's properties when you list out the methods.
Visual Basic Sub and Function routines are both considered methods in reflection. The only difference is that a Sub has no return value. If a method is a Function, and thus does have a return value, reflection allows you to discover the type of that return value.
Methods may have calling parameters. Reflection allows you to discover the calling parameters of a method, if there are any, using the GetParameters method of the MethodInfo instance for the method. The GetParameters method returns an array of ParameterInfo objects.
Using parameters, if any, a method can be invoked with the Invoke method of the MethodInfo instance. Suppose, for example, that the current class has a function named CalculateFee that takes an integer for customer ID and returns a decimal value.
Here is sample code to print the parameters for the method:
Dim MyMethodInfo As MethodInfo = Me.GetType.GetMember("CalculateFee")(0) For Each ParamInfo In MyMethodInfo.GetParameters Console.WriteLine("Parameter name:" & ParamInfo.Name) Console.WriteLine("Parameter type:" & ParamInfo.ParameterType.Name) Next
To set up the parameter values and invoke the method, the code would look like this:
Dim MyMethodInfo2 As MethodInfo = _ Me.GetType.GetMember("CalculateFee")(0) 'Create array of objects to serve as parameters. 'In this case, only one integer is needed. Dim MyParameters() As Object = {4321} Dim oReturn As Object oReturn = MyMethodInfo2.Invoke(Me, MyParameters) ’ Now cast oReturn to Decimal
The code download for this chapter includes a WPF program (AssemblyBrowser) that enables you to locate an assembly on disk and load the types from that assembly. For any type available in the assembly, you can then load all the methods of the type.