There are situations in which you need to implement
logic for performing some tasks depending on user choices. This kind of a
situation is not uncommon. The problem is when you cannot predetermine
the code required for executing actions depending on user input. Think
of code generators: Such tools know how to generate code but cannot
predetermine what code has to be generated until the users specify their
requirements. Also think of assemblies external from your application.
In some cases you might want to use types from an external assembly; in
other cases you might just want to get information on types provided by
the assembly; and in other cases you might want to reach members with
limited scope visibility that you could not reach by simply adding a
reference. Reflection is a key part in the .NET Framework that enables
accomplishing all these mentioned scenarios.
Introducing Reflection
Reflection is an
important part of the .NET Framework that provides the ability for
interrogating assemblies’ metadata and collecting information on types
exposed by assemblies. Reflection also enables invoking code from
external assemblies and generating code on-the-fly. You can take
advantage of Reflection by using objects exposed by the System.Reflection
namespace. It can be particularly useful when you need to generate code
according to some user input or when you are in late bound scenarios
where making decisions on what code must be invoked (or generated) is
something determined at runtime. Before putting your hands on code, it
is necessary to get an overview of how assemblies are structured so that
you can have a better understanding of what kind of information you can
investigate with the Reflection.
Understanding Assemblies’ Metadata
As you know, when you build an
executable with Visual Basic, you build a .NET assembly. An assembly is
basically a container of metadata and code. Metadata is information that
the CLR uses in correctly loading and running the assembly. Figure 1 represents how an assembly is structured.
The Assembly Metadata, also known as assembly manifest,
basically provides assembly information such as the name, version,
culture, copyright information, and signature. The Type Metadata
contains information on types defined within the assembly, such as class
names and names of class members, including their parameters. The Code
part is the actual Intermediate Language code that will be executed when
the assembly is loaded. The Resources block contains all resources
required by the assembly, such as images, icons, and strings. Also
notice that types within an assembly can be grouped into multiple
modules. A module is a container of types whereas an assembly is a
container of modules. With Reflection you can inspect metadata and code
from an assembly using Visual Basic code, including assembly
information.
Note
When
talking about assemblies, we usually refer to single file executables.
Assemblies can be composed of multiple linked files; keep in mind that
assembly metadata needs to reside only in the main assembly. This is a
special case and cannot be accomplished with Visual Studio (you should
use manually MSBuild), but it is something that it is worth mentioning.
Preparing a Sample Assembly
Before showing Reflection
capabilities, a good idea is to prepare an appropriate code example.
First, create a new class library project and name it People. The goal of the library is to expose a special implementation of the Person
class, with interfaces and enumerations implementations for a better
demonstration on Reflection. When ready, write the code in Listing 1, which is quite simple.
Listing 1. Preparing Code for Reflection
Imports System.Text
Public Enum Genders Male = 0 Female = 1 End Enum
Public Interface IPerson Property FirstName As String Property LastName As String Property Age As Integer Property Gender As Genders Event InstanceCreated() Function BuildFullName() As String End Interface
Public Class Person Implements IPerson
Public Property FirstName As String Implements IPerson.FirstName Public Property Gender As Genders Implements IPerson.Gender Public Property LastName As String Implements IPerson.LastName Public Property Age As Integer Implements IPerson.Age Public Event InstanceCreated() Implements IPerson.InstanceCreated
Public Overridable Function BuildFullName() As String _ Implements IPerson.BuildFullName Dim fullName As New StringBuilder fullName.Append(LastName) fullName.Append(" ") fullName.Append(FirstName) fullName.Append(", ") fullName.Append(Gender.ToString) fullName.Append(", of age ") fullName.Append(Age.ToString)
Return fullName.ToString End Function End Class
|
Build
the project; then add a new Console project to the current solution.
Finally add a reference to the People class library so that, just for
demo purposes, you can load the assembly for Reflection without
specifying the full path.
Getting Assembly Information
You get assembly metadata information creating an instance of the System.Reflection.Assembly
class. This class provides both static and instance members for
accessing assembly information. Typically you use one of the methods
summarized in Table 1 to load an assembly for getting information.
Table 1. Methods for Loading an Assembly
Method | Description |
---|
GetAssembly | Loads an assembly containing the specified type |
GetCallingAssembly | Gets the assembly that stores the code that invoked the current method |
GetExecutingAssembly | Returns the instance of the current assembly |
GetEntryAssembly | Returns the instance of the assembly that ran the current process |
Load | Loads the specified assembly into the current application domain |
LoadFile | Loads the specified assembly from the specified path |
LoadFrom | Loads the specified assembly into the current application domain, given the specified path |
ReflectionOnlyLoad | Like Load, but allows only Reflection inspection and not code execution |
ReflectionOnlyLoadFrom | Like LoadFrom, but allows only Reflection inspection and not code execution |
When
you get the instance of the assembly you want to inspect, you can
access information via some useful properties. The code in Listing 2 shows how to accomplish this. (See comments for explanations.)
Listing 2. Inspecting Assembly Information
Imports System.Reflection
Module GettingAsmInfo
Sub Main()
'Infers System.Reflection.Assembly Dim asm = Assembly.ReflectionOnlyLoadFrom("People.dll")
With asm 'Gets the full assembly name with 'version and culture Console.WriteLine("Assembly name:") Console.WriteLine(.FullName) 'Gets whether the assembly is fully trusted Console.WriteLine("Is full-trust: {0}", .IsFullyTrusted) 'Gets the assembly entry point. If empty, the 'constructor is the entry point Console.WriteLine("The entry point method is: {0}", .EntryPoint) 'Gets the .NET version that the 'assembly was built upon Console.WriteLine("Image runtime version: {0}", .ImageRuntimeVersion) 'Gets whether the assembly was loaded from 'the GAC Console.WriteLine("Loaded from the GAC: {0}", .GlobalAssemblyCache) 'Gets the assembly location Console.WriteLine("Assembly path: {0}", .Location)
'Gets an array of modules loaded 'by the assembly Console.WriteLine("Loaded modules: ") For Each item As System.Reflection.Module _ In .GetLoadedModules Console.WriteLine(" {0}", item.Name) Next End With Console.ReadLine() End Sub End Module
|
Notice how the code uses the ReflectionOnlyLoadFrom
method to enable only inspection without code execution capabilities.
If you run the preceding code, you get the following result:
Assembly name:
People, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Is full-trust: True
The entry point method is:
Image runtime version: v4.0.21006
Loaded from the GAC: False
Assembly path: C:\Users\Alessandro\documents\visual studio
2010\Projects\Reflection\Reflection\bin\Debug\People.dll
Loaded modules:
People.dll
Notice that the RTM version number of the .NET Framework 4 has a build number different than the previous one. The Assembly.GetModules method returns an array of modules loaded by the instance of the assembly. Other interesting methods are GetExportedTypes, which return an array of publicly visible types, and GetFiles, which returns an array of FileStream
objects, each representing a file in the assembly’s resources.
Inspecting assembly information is just the first level of Reflection.
The next step is inspecting types.