eflection in .NET is an exciting new tool for most developers. This article shows you how to use reflection to discover objects at runtime that you did not know existed at design time, create an instance of those objects, and execute methods of those objects.
What Is Reflection?
Reflection is a means of discovering information about objects at runtime. This information can be used to execute methods and retrieve property values of these objects in a dynamic manner. Code written for the .NET Framework automatically reflects, or describes, itself. It does so via compiler-generated metadata. You can therefore use reflection in your applications to query managed code about its types (including class names, methods, and parameters) and then interact with this information?this includes executing the discovered code. You can also use reflection to generate code at runtime, compile it, and run it.
What’s the Point of Reflection?
The most obvious example of reflection is a type or object browser. Think of the Visual Studio .NET object browser. This utility can display classes exposed by an assembly, methods on those classes, parameters for those methods, etc. (see Figure 1 for an example). In the past, some of this information was available to us via COM type libraries. With .NET this application data is available from the assembly itself. All .NET assemblies are self-describing?that is, they contain metadata about their types. You can query this metadata to learn about a given object. (See Sidebar: Metadata)
|The Namespaces and Classes
There are two namespaces that contain the Reflection classes, System.Reflection and System.Reflection.Emit. The Reflection namespace contains objects related to type discovery and execution while the Reflection.Emit namespace dynamically generates code at runtime (this is the focus of part 2 of this article). Table 1 provides a quick and easy reference to the key classes you’ll use when working with reflection.
Reflection Discovery: Searching and Filtering
This code is useful for looping through all of the public types exposed on a given assembly, but what if you have a large assembly or are simply after specific types such as a constructor or a property? If you’ve spent any time at all looking at the .NET Framework Class Library, you know that one Assembly can contain a large number of types. You may not need to know every type; you might prefer to filter or search for a very specific type or types. Fortunately, the System.Type class exposes a number of methods for accessing, filtering, and searching for specific types inside a given assembly.
Here is a class with three empty constructors and an empty method. Now assume that you want to access the constructor that takes one integer value as its only parameter. To do so you would use the GetConstructor method of the Type class. This method allows you to pass an array of objects that represent parameters for a given constructor. When executed, the method searches a type for any constructor that matches the signature defined by the array of parameters. GetConstructor then returns a ConstructorInfo object for your use (perhaps you want to invoke the constructor). For example, you would first create the array of parameters as follows:
Finally, you would then call GetConstructor of your Type object as follows:
Similarly, Type.GetMethod provides you with direct access to the methods on a given object.
This method returns a MethodInfo instance for your use. It simply takes the name of the given method as a parameter. For example:
You might wonder, “What if I have two methods with the same name but different signatures in the same class?” Well, in the previous case you would get an ambiguous matching error. However, there are a number of versions of all of these direct access methods that allow you to pinpoint specific types. For instance the GetConstructor method can filter methods based on an array of parameters; or you could filter based on calling conventions, return type, etc. You can apply this same pattern to directly access a specific property or an event contained by your type.
A typical filter involves setting binding flags. Binding flags represent search criteria. You use values of the BindingFlags enumeration to represent things such as public or non-public types. You can also indicate flags or static members. You can even combine flags to further narrow your search. Consider the following basic class:
You can see that there are three public methods, two of which are static (or shared). Suppose that through reflection you want to find all public static methods for the SomeClass Type. You would call GetMethods and pass BindingFlag enumeration values such as the bindingAttr parameter. The following code retrieves an array of all methods in SomeClass that are both public and static:
You can use this same technique to return private types (provided you have the correct permission). To do so you would use the BindingFlag enumeration value BindingFlags.NonPublic. This can be combined with BindingFlags.Instance to return all instance methods that are private. This same pattern can be applied to return constructors, properties, and events.
Notice that the following Visual Basic class defines three fields: two private and one public.
Suppose that you need to use the FindMembers method of the Type class to return the private fields on an instance of SomeClass and display their values. You would do so by indicating the value Field of the enumeration MemberTypes for the memberTypes parameter of FindMembers. You then can indicate your BindingFlags, and FindMembers will return an array of MemberInfo objects that match your search criteria. The following code snippet provides an example:
Once you find the target members, the code converts them into actual FieldInfo objects and is able to query them for their values.
The filterCriteria parameter can be any .NET object. You can use this parameter to define your custom search criteria. This can be as simple as a string or as involved as a custom object. Here is an example.
Image you have a class called SomeClass and that it defines three properties: Name, Id, and Type. Now suppose that you need a filter that searches classes to find only the properties Name and Id. We’ve provided a very simple example and of course you could return all properties, loop through them, and filter out those not named Name or Id. Take a look at the console application in Listing 1.
This application defines the delegate, MySearchDelegate, to customize the search. It creates the custom object, filterObject, with two fields that help define our search criteria. The application then calls FindMembers and indicates that you want all Property types. When a Property type is found the application raises the MySearchDelegate and passes the filterCriteria instance. The delegate then simply makes a decision based on the member name and returns True or False indicating whether or not the search passed the custom test. (See Sidebar: Custom Attributes)
Executing Discovered Code
The process for executing discovered code follows these basic steps:
You’ve already seen how to load an assembly and search for types in that assembly. Once you know the type you are after, you can use the System.Activator class to return an instance of the type. You’ll use one of the CreateInstance methods of the Activator class. CreateInstance allows you to specify the object you want created and optionally the parameters used in the object’s constructors. Here is a simple example where an object’s default constructor takes no parameters:
Suppose that you want to create an instance of a given object with a constructor that took parameters. You can do so by passing these values as an array to CreateInstance. Each value needs to be of the same type and in the same order of the constructor’s signature. Imagine that the type you were trying to create had the following constructor:
Your first job would be to query the constructor for parameters. Once you’ve identified the constructor, you can get its parameters using the GetParameters method of the ConstructorInfo class. GetParameters will return an array of ParameterInfo objects that will let you determine a parameter’s order, its name, and its data type. You can then build your own array of parameter values and pass it to CreateInstance. Here is a basic example.
Suppose you have a class called SomeClass that has a constructor that takes one parameter that is of the type String. Let’s also suppose you have a reference to SomeClass called myType. Finally, let’s assume you have a reference (called ci) to SomeClass’ constructor as a ConstructorInfo instance. To get a list of parameters for the given constructor (ci) you call GetParameters as follows:
Then you create your own array of the same size as the number of parameters returned by GetParameters.
Values are set for every parameter in the array:
Finally, you call CreateInstance, passing in both your type and the values for the type’s constructor’s parameters.
Now that you have an instance (o) for your object (SomeClass), let’s look at how you might execute one of its methods. The same process of querying for parameters and passing them into a constructor works for methods. Let’s suppose that SomeClass has a method called SomeMethod that you want to invoke. To keep this simple let’s suppose that SomeMethod takes no parameters (as this is the same process outlined above). To invoke SomeMethod you need to get a reference to the method as a MethodInfo object. You can search for methods on your type with either GetMethod or GetMethods. Let’s use GetMethod and pass the method name as a string:
You have both an instance to SomeClass and a reference (mi) to the method you wish to call so you can use MethodInfo.Invoke to call your target method. You’ll pass your object instance that contains your method and an array of parameters that the method takes (in this case Nothing). For example:>
You’ve now successfully created an instance of an object not necessarily known at design time, found a method on that object, and invoked the method. You can easily extrapolate this example to create a utility such as a testing tool. Suppose that you allow a user to select an assembly. You could list all classes and methods in the given assembly. The user could select a method at run time. You could use discovered information about the class and method to present the user with a form to exercise the method. The user could then enter values for the given method and the testing tool would invoke the method and return the results?all without knowing a thing about the assembly at design time. (See Sidebar: Reflection Security)
Putting It All Together (A Simple Type Browser)
To show how easy it is to create a .NET type browser using reflection we put together a simple application called Discover that will allow you to browse a directory for any stored assembly (.DLL or .EXE). Once you select an assembly, you can click the Discover button and the application will fill a Tree control with information about that assembly. Figure 2 shows the Discover utility in action.
The information that the Discover application displays is all types in a given assembly, all the members of a given type, all the methods of a given type, and each method’s parameters and data types. Displaying this information with reflection is remarkably easy. Listing 2 presents the code that loads the assembly and builds the tree. This code should be familiar to you, as it has been presented in previous sections of this article.
Hopefully the information in this article will allow you to add reflection as a new tool to your programmer’s tool belt. It may not be the tool you’ll reach for in typical programming situations, but given the right task, it can provide you with a lot of power.
In Part 2 of our reflection series we’ll explore using the System.Reflection.Emit namespace to generate code at runtime.
All of the code for this article can be downloaded from www.brilliantstorm.com/resources.