You may occasionally need to interrogate an object about its members at run time, or even invoke a method on that object without knowing its exact type. Some of you will recognize the latter task as late-bound, or IDispatch method, invocation. In .NET, the Reflection API classes, most of which are housed in the System.Reflection namespace, enable developers to determine type information at run time and perform late-bound method invocation. Reflection is also how Visual Studio .NET’s Intellisense prompts you with all those method and parameter lists while you’re writing code.
In this article, I’ll walk you through two example programs—a run-time class information reporter and a convoluted Hello World application—that illustrate late-bound method invocation.
Getting type information with System.Type
The System.Type class is the cornerstone of .NET’s Reflection API. With it, you can glean any information about a type you could possibly need. All types inherit a GetType method from System.Object, which returns a Type object representing that type’s definition. It’s also possible to create a Type without an instance variable by passing the type’s fully qualified name (e.g., System.Threading.Thread) to the static Type.GetType method.
The Type class contains methods that determine a type’s base class, whether or not the type is a reference or value, and any methods, properties, or fields the type exposes. See Table A for a selected list of these methods.
The ClassInfo example
As you can see, you can gather quite a bit of information about a particular type using the Type class. In Listing A, you’ll find the C# source for a small sample app, called ClassInfo, that lists all information for a specific class out to the console. Figure A shows the results of running ClassInfo on System.Object.
|ClassInfo’s report on System.Object|
For simplicity’s sake, ClassInfo works only with reference types; it will examine only types for which the isClass property happens to be true. Once it determines that the specified type is actually a class, ClassInfo lists any declaration modifiers attached to the class. For example, if the isAbstract property is true, the text “Abstract” is output to the console. Next, a list of the class’s methods is generated, using the GetMethods method, which returns an array of System.Reflection.MethodInfo objects representing the class’s complete interface, minus constructors and public fields.
Each MethodInfo object represents a single method and includes a group of isX properties you can use to determine how the method is declared: public, private, sealed, and so on. MethodInfo also exposes a GetParameters method, which will return an array of System.Reflection.ParameterInfo objects, each representing one of the method’s declared parameters. ClassInfo iterates through all methods exposed by the class, listing the properties and parameters declared for each of them.
Although ClassInfo doesn’t make use of this capability, the Type class also allows you to iterate through the properties, constructors, events, and fields of a class in a fashion similar to the way I iterate through methods in ClassInfo. You’d do so using the appropriate get method found in Table A.
A complicated way to say "Hello"
Programmers joke about the evolution the canonical “Hello World” beginner’s program goes through as a developer increases his or her experience. It starts out with the simple one-liner we all know and love and goes through several iterations, ending up with several pages of heavily object-oriented C++ code which produces the same output as the original one-liner. Keep this in mind as you check out the C# source in Listing B, which uses Reflection’s method invocation to provide a similarly roundabout take on the traditional “Hello World” program.
The real action here takes place in the LateBoundCall method, which accepts an instance of HelloHello, cast to System.Object, and uses the InvokeMember method of the Type class to call HelloHello.SaySomething using late-binding. This sort of functionality becomes useful whenever you need to invoke a method on an object without knowing its exact type. InvokeMember accepts a few parameters indicating the name and type of method it is to call, how the call should be made, and any arguments it should pass to the invoked method. The parameters for the simplest override of InvokeMember and the meaning of each are listed in Table B.
Although .NET’s Reflection features are a powerful way to determine the properties of a given object at run time, you should use them only when necessary. Examining the metadata for a class and late-bound method invocation are both expensive operations.