
Pro CSharp 2008 And The .NET 3.5 Platform [eng]
.pdf
532 CHAPTER 16 ■ TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING
Displaying Various Odds and Ends
Last but not least, you have one final helper method that will simply display various statistics (indicating whether the type is generic, what the base class is, whether the type is sealed, and so forth) regarding the incoming type:
// Just for good measure.
static void ListVariousStats(Type t)
{
Console.WriteLine("***** Various Statistics *****");
Console.WriteLine("Base class is: {0}", t.BaseType); Console.WriteLine("Is type abstract? {0}", t.IsAbstract); Console.WriteLine("Is type sealed? {0}", t.IsSealed); Console.WriteLine("Is type generic? {0}", t.IsGenericTypeDefinition); Console.WriteLine("Is type a class type? {0}", t.IsClass); Console.WriteLine();
}
Implementing Main()
The Main() method of the Program class prompts the user for the fully qualified name of a type. Once you obtain this string data, you pass it into the Type.GetType() method and send the extracted System.Type into each of your helper methods. This process repeats until the user enters Q to terminate the application:
static void Main(string[] args)
{
Console.WriteLine("***** Welcome to MyTypeViewer *****");
string typeName = ""; bool userIsDone = false;
do
{
Console.WriteLine("\nEnter a type name to evaluate"); Console.Write("or enter Q to quit: ");
// Get name of type.
typeName = Console.ReadLine();
// Does user want to quit?
if (typeName.ToUpper() == "Q")
{
userIsDone = true; break;
}
// Try to display type. try
{
Type t = Type.GetType(typeName); Console.WriteLine(""); ListVariousStats(t);


534 CHAPTER 16 ■ TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING
Reflecting on Generic Types
When you call Type.GetType() in order to obtain metadata descriptions of generic types, you must make use of a special syntax involving a “back tick” character (`) followed by a numerical value that represents the number of type parameters the type supports. For example, if you wish to print out the metadata description of List<T>, you would need to pass the following string into your application:
System.Collections.Generic.List`1
Here, we are using the numerical value of 1, given that List<T> has only one type parameter. However, if you wish to reflect over Dictionary<TKey, TValue>, you would supply the value 2:
System.Collections.Generic.Dictionary`2
Reflecting on Method Parameters and Return Values
So far, so good! Let’s make one minor enhancement to the current application. Specifically, you will update the ListMethods() helper function to list not only the name of a given method, but also the return value and incoming parameters. The MethodInfo type provides the ReturnType property and GetParameters() method for these very tasks. In the following code, notice that you are building a string type that contains the type and name of each parameter using a nested foreach loop:
static void ListMethods(Type t)
{
Console.WriteLine("***** Methods *****");
MethodInfo[] mi = t.GetMethods(); foreach (MethodInfo m in mi)
{
// Get return value.
string retVal = m.ReturnType.FullName; string paramInfo = "(";
// Get params.
foreach (ParameterInfo pi in m.GetParameters())
{
paramInfo += string.Format("{0} {1} ", pi.ParameterType, pi.Name);
}
paramInfo += ")";
// Now display the basic method sig.
Console.WriteLine("->{0} {1} {2}", retVal, m.Name, paramInfo);
}
Console.WriteLine();
}
If you now run this updated application, you will find that the methods of a given type are much more detailed. Figure 16-3 shows the method metadata of the System.Globalization. GregorianCalendar type.


536 CHAPTER 16 ■ TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING
Dynamically Loading Assemblies
In the previous chapter, you learned all about how the CLR consults the assembly manifest when probing for an externally referenced assembly. However, there will be many times when you need to load assemblies on the fly programmatically, even if there is no record of said assembly in the manifest. Formally speaking, the act of loading external assemblies on demand is known as a dynamic load.
System.Reflection defines a class named Assembly. Using this type, you are able to dynamically load an assembly as well as discover properties about the assembly itself. Using the Assembly type, you are able to dynamically load private or shared assemblies, as well as load an assembly located at an arbitrary location. In essence, the Assembly class provides methods (Load() and LoadFrom() in particular) that allow you to programmatically supply the same sort of information found in a client-side *.config file.
To illustrate dynamic loading, create a brand-new Console Application named External AssemblyReflector. Your task is to construct a Main() method that prompts for the friendly name of an assembly to load dynamically. You will pass the Assembly reference into a helper method named DisplayTypes(), which will simply print the names of each class, interface, structure, enumeration, and delegate it contains. The code is refreshingly simple:
using System;
using System.Reflection;
using System.IO; // For FileNotFoundException definition.
namespace ExternalAssemblyReflector
{
class Program
{
static void DisplayTypesInAsm(Assembly asm)
{
Console.WriteLine("\n***** Types in Assembly *****");
Console.WriteLine("->{0}", asm.FullName); Type[] types = asm.GetTypes();
foreach (Type t in types) Console.WriteLine("Type: {0}", t);
Console.WriteLine("");
}
static void Main(string[] args)
{
Console.WriteLine("***** External Assembly Viewer *****");
string asmName = ""; bool userIsDone = false; Assembly asm = null;
do
{
Console.WriteLine("\nEnter an assembly to evaluate"); Console.Write("or enter Q to quit: ");
// Get name of assembly. asmName = Console.ReadLine();


538 CHAPTER 16 ■ TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING
Reflecting on Shared Assemblies
The Assembly.Load() method has been overloaded a number of times. One variation allows you to specify a culture value (for localized assemblies) as well as a version number and public key token value (for shared assemblies). Collectively speaking, the set of items identifying an assembly is termed the display name. The format of a display name is a comma-delimited string of name/value pairs that begins with the friendly name of the assembly, followed by optional qualifiers (that may appear in any order). Here is the template to follow (optional items appear in parentheses):
Name (,Version = major.minor.build.revision) (,Culture = culture token) (,PublicKeyToken= public key token)
When you’re crafting a display name, the convention PublicKeyToken=null indicates that binding and matching against a non–strongly named assembly is required. Additionally, Culture="" indicates matching against the default culture of the target machine, for example:
// Load version 1.0.982.23972 of CarLibrary using the default culture.
Assembly a = Assembly.Load(
@"CarLibrary, Version=1.0.982.23972, PublicKeyToken=null, Culture=""");
Also be aware that the System.Reflection namespace supplies the AssemblyName type, which allows you to represent the preceding string information in a handy object variable. Typically, this class is used in conjunction with System.Version, which is an OO wrapper around an assembly’s version number. Once you have established the display name, it can then be passed into the overloaded Assembly.Load() method:
// Make use of AssemblyName to define the display name.
AssemblyName asmName; asmName = new AssemblyName(); asmName.Name = "CarLibrary";
Version v = new Version("1.0.982.23972"); asmName.Version = v;
Assembly a = Assembly.Load(asmName);
To load a shared assembly from the GAC, the Assembly.Load() parameter must specify a PublicKeyToken value. For example, assume you wish to load version 2.0.0.0 of the System.Windows. Forms.dll assembly provided by the .NET base class libraries. Given that the number of types in this assembly is quite large, the following application only prints out the names of public enums, using a simple LINQ query:
using System;
using System.Reflection; using System.IO;
using System.Linq;
namespace SharedAsmReflector
{
public class SharedAsmReflector
{
private static void DisplayInfo(Assembly a)
{
Console.WriteLine("***** Info about Assembly *****");
Console.WriteLine("Loaded from GAC? {0}", a.GlobalAssemblyCache); Console.WriteLine("Asm Name: {0}", a.GetName().Name); Console.WriteLine("Asm Version: {0}", a.GetName().Version); Console.WriteLine("Asm Culture: {0}",
a.GetName().CultureInfo.DisplayName); Console.WriteLine("\nHere are the public enums:");

CHAPTER 16 ■ TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING |
539 |
// Use a LINQ query to find the public enums.
Type[] types = a.GetTypes();
var publicEnums = from pe in types where pe.IsEnum && pe.IsPublic select pe;
foreach (var pe in publicEnums)
{
Console.WriteLine(pe);
}
}
static void Main(string[] args)
{
Console.WriteLine("***** The Shared Asm Reflector App *****\n");
// Load System.Windows.Forms.dll from GAC. string displayName = null;
displayName = "System.Windows.Forms," + "Version=2.0.0.0," + "PublicKeyToken=b77a5c561934e089," + @"Culture=""";
Assembly asm = Assembly.Load(displayName); DisplayInfo(asm); Console.WriteLine("Done!"); Console.ReadLine();
}
}
}
■Source Code The SharedAsmReflector project is included in the Chapter 16 subdirectory.
At this point you should understand how to use some of the core types defined within the System.Reflection namespace to discover metadata at runtime. Of course, I realize despite the “cool factor,” you likely will not need to build custom object browsers at your place of employment. Do recall, however, that reflection services are the foundation for a number of very common programming activities, including late binding.
Understanding Late Binding
Simply put, late binding is a technique in which you are able to create an instance of a given type and invoke its members at runtime without having hard-coded compile-time knowledge of its existence. When you are building an application that binds late to a type in an external assembly, you have no reason to set a reference to the assembly; therefore, the caller’s manifest has no direct listing of the assembly.
At first glance, it is not easy to see the value of late binding. It is true that if you can “bind early” to a type (e.g., set an assembly reference and allocate the type using the C# new keyword), you should opt to do so. For one reason, early binding allows you to determine errors at compile time, rather than at runtime. Nevertheless, late binding does have a critical role in any extendable application you may be building. You will have a chance to build such an “extendable” program at the end of this chapter in the section “Building an Extendable Application”; until then, we need to examine the role of the Activator type.


CHAPTER 16 ■ TYPE REFLECTION, LATE BINDING, AND ATTRIBUTE-BASED PROGRAMMING |
541 |
MethodInfo, you are then able to call MiniVan.TurboBoost using Invoke(). MethodInfo.Invoke() requires you to send in all parameters that are to be given to the method represented by MethodInfo. These parameters are represented by an array of System.Object types (as the parameters for a given method could be any number of various entities).
Given that TurboBoost() does not require any parameters, you can simply pass null (meaning “this method has no parameters”). Update your Main() method as follows:
static void Main(string[] args)
{
...
// Get metadata for the Minivan type.
Type miniVan = a.GetType("CarLibrary.MiniVan");
// Create the Minivan on the fly.
object obj = Activator.CreateInstance(miniVan); Console.WriteLine("Created a {0} using late binding!", obj);
// Get info for TurboBoost.
MethodInfo mi = miniVan.GetMethod("TurboBoost");
// Invoke method ('null' for no parameters). mi.Invoke(obj, null);
Console.ReadLine();
}
At this point you are happy to see the message box in Figure 16-5.
Figure 16-5. Late-bound method invocation
Invoking Methods with Parameters
To illustrate how to dynamically invoke a method that does take some number of parameters, assume you have updated the MiniVan type created in the previous chapter with a new method named TellChildToBeQuiet():
// Quiet down the troops...
public void TellChildToBeQuiet(string kidName, int shameIntensity)
{
for(int i = 0 ; i < shameIntensity; i++) MessageBox.Show(string.Format("Be quiet {0} !!", kidName));
}
TellChildToBeQuiet() takes two parameters: a string representing the child’s name and an integer representing your current level of frustration. When using late binding, parameters are packaged as an array of System.Objects. To invoke the new method, update the Main() method as follows: