
Pro CSharp And The .NET 2.0 Platform (2005) [eng]
.pdf
394 C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G
Examining a TypeRef
Recall that an assembly’s metadata will describe not only the set of internal types (Car, EngineState, etc.), but also any external types the internal types reference. For example, given that CarLibrary.dll has defined two enumerations, you find a TypeRef block for the System.Enum type:
TypeRef #1 (01000001)
-------------------------------------------------------
Token: 0x01000001
ResolutionScope: 0x23000001
TypeRefName: System.Enum MemberRef #1
-------------------------------------------------------
Member: (0a00000f) ToString: CallCnvntn: [DEFAULT] hasThis
ReturnType: String No arguments.
Documenting the Defining Assembly
The ildasm.exe metadata window also allows you to view the .NET metadata that describes the assembly itself using the Assembly token. As you can see from the following (partial) listing, information documented within the Assembly table is (surprise, surprise!) the same information that can be viewable via the MANIFEST icon. Here is a partial dump of the manifest of CarLibrary.dll (version 2.0.0.0):
Assembly
-------------------------------------------------------
Token: 0x20000001
Name : CarLibrary
Public Key : 00 24 00 00 04 80 00 00 // Etc...
Hash Algorithm : 0x00008004
Major Version: 0x00000002
Minor Version: 0x00000000
Build Number: 0x00000000
Revision Number: 0x00000000
Locale: <null>
Flags : [SideBySideCompatible] (00000000)
Documenting Referenced Assemblies
In addition to the Assembly token and the set of TypeDef and TypeRef blocks, .NET metadata also makes use of “AssemblyRef #n” tokens to document each external assembly. Given that the CarLibrary.dll makes use of the MessageBox type, you find an AssemblyRef for System.Windows.Forms, for example:
AssemblyRef #2
-------------------------------------------------------
Token: 0x23000002
Public Key or Token: b7 7a 5c 56 19 34 e0 89 Name: System.Windows.Forms
Version: 2.0.3600.0 Major Version: 0x00000002 Minor Version: 0x00000000

C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G |
395 |
Build Number: 0x00000e10
Revision Number: 0x00000000
Locale: <null>
HashValue Blob:
Flags: [none] (00000000)
Documenting String Literals
The final point of interest regarding .NET metadata is the fact that each and every string literal in your code base is documented under the User Strings token, for example:
User Strings
-------------------------------------------------------
70000001 |
: |
(11) |
L"Car 2.0.0.0" |
|
|
70000019 |
: |
(11) |
L"Jamming {0}" |
|
|
70000031 |
: |
(13) |
L"Quiet time... |
" |
|
7000004d |
: |
(14) |
L"Ramming speed!" |
|
|
7000006b |
: |
(19) |
L"Faster is better... |
" |
|
70000093 |
: |
(16) |
L"Time to call AAA" |
|
|
700000b5 |
: |
(16) |
L"Your car is dead" |
|
|
700000d7 |
: |
( 9) L"Be quiet " |
|
|
|
700000eb : |
( 2) L"!!" |
|
|
Now, don’t be too concerned with the exact syntax of each and every piece of .NET metadata. The bigger point to absorb is that .NET metadata is very descriptive and lists each internally defined (and externally referenced) type found within a given code base.
The next question on your mind may be (in the best-case scenario) “How can I leverage this information in my applications?” or (in the worst-case scenario) “Why should I care about metadata?” To address both points of view, allow me to introduce .NET reflection services. Be aware that the usefulness of the topics presented over the pages that follow may be a bit of a head-scratcher until this chapter’s endgame. So hang tight.
■Note You will also find a number of CustomAttribute tokens displayed by the MetaInfo window, which documents the attributes applied within the code base. You’ll learn about the role of .NET attributes later in this chapter.
Understanding Reflection
In the .NET universe, reflection is the process of runtime type discovery. Using reflection services, you are able to programmatically obtain the same metadata information displayed by ildasm.exe using a friendly object model. For example, through reflection, you can obtain a list of all types contained within a given assembly (or *.netmodule), including the methods, fields, properties, and events defined by a given type. You can also dynamically discover the set of interfaces supported by a given class (or structure), the parameters of a method, and other related details (base classes, namespace information, manifest data, and so forth).
Like any namespace, System.Reflection contains a number of related types. Table 12-1 lists some of the core items you should be familiar with.

396 C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G
Table 12-1. A Sampling of Members of the System.Reflection Namespace
Type |
Meaning in Life |
Assembly |
This class (in addition to numerous related types) contains a number of |
|
methods that allow you to load, investigate, and manipulate an assembly. |
AssemblyName |
This class allows you to discover numerous details behind an assembly’s |
|
identity (version information, culture information, and so forth). |
EventInfo |
This class holds information for a given event. |
FieldInfo |
This class holds information for a given field. |
MemberInfo |
This is the abstract base class that defines common behaviors for the |
|
EventInfo, FieldInfo, MethodInfo, and PropertyInfo types. |
MethodInfo |
This class contains information for a given method. |
Module |
This class allows you to access a given module within a multifile assembly. |
ParameterInfo |
This class holds information for a given parameter. |
PropertyInfo |
This class holds information for a given property. |
|
|
To understand how to leverage the System.Reflection namespace to programmatically read
.NET metadata, you need to first come to terms with the System.Type class.
The System.Type Class
The System.Type class defines a number of members that can be used to examine a type’s metadata, a great number of which return types from the System.Reflection namespace. For example,
Type.GetMethods() returns an array of MethodInfo types, Type.GetFields() returns an array of
FieldInfo types, and so on. The complete set of members exposed by System.Type is quite expansive; however, Table 12-2 offers a partial snapshot of the members supported by System.Type (see the
.NET Framework 2.0 SDK documentation for full details).
Table 12-2. Select Members of System.Type
Type |
Meaning in Life |
IsAbstract |
These properties (among others) allow you |
IsArray |
to discover a number of basic traits about |
IsClass |
the Type you are referring to (e.g., if it is an |
IsCOMObject |
abstract method, an array, a nested class, and |
IsEnum |
so forth). |
IsGenericTypeDefinition |
|
IsGenericParameter |
|
IsInterface |
|
IsPrimitive |
|
IsNestedPrivate |
|
IsNestedPublic |
|
IsSealed |
|
IsValueType |
|
GetConstructors() |
These methods (among others) allow you to |
GetEvents() |
obtain an array representing the items (interface, |
GetFields() |
method, property, etc.) you are interested in. |
GetInterfaces() |
Each method returns a related array |
GetMembers() |
(e.g., GetFields() returns a FieldInfo array, |
GetMethods() |
GetMethods() returns a MethodInfo array, etc.). |
GetNestedTypes() |
Be aware that each of these methods has a |
GetProperties() |
singular form (e.g., GetMethod(), GetProperty(), |
|
etc.) that allows you to retrieve a specific item by |
|
name, rather than an array of all related items. |

C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G |
397 |
|
|
|
|
Type |
Meaning in Life |
|
FindMembers() |
This method returns an array of MemberInfo |
|
|
types based on search criteria. |
|
GetType() |
This static method returns a Type instance |
|
|
given a string name. |
|
InvokeMember() |
This method allows late binding to a given item. |
|
|
|
|
Obtaining a Type Reference Using System.Object.GetType()
You can obtain an instance of the Type class in a variety of ways. However, the one thing you cannot do is directly create a Type object using the new keyword, as Type is an abstract class. Regarding your first choice, recall that System.Object defines a method named GetType(), which returns an instance of the Type class that represents the metadata for the current object:
// Obtain type information using a SportsCar instance.
SportsCar sc = new SportsCar(); Type t = sc.GetType();
Obviously, this approach will only work if you have compile-time knowledge of the type (SportsCar in this case). Given this restriction, it should make sense that tools such as ildasm.exe do not obtain type information by directly calling System.Object.GetType() for each type, given the ildasm.exe was not compiled against your custom assemblies!
Obtaining a Type Reference Using System.Type.GetType()
To obtain type information in a more flexible manner, you may call the static GetType() member of the System.Type class and specify the fully qualified string name of the type you are interested in examining. Using this approach, you do not need to have compile-time knowledge of the type you are extracting metadata from, given that Type.GetType() takes an instance of the omnipresent
System.String.
The Type.GetType() method has been overloaded to allow you to specify two Boolean parameters, one of which controls whether an exception should be thrown if the type cannot be found, and the other of which establishes the case sensitivity of the string. To illustrate, ponder the following:
//Obtain type information using the static Type.GetType() method.
//(don't throw an exception if SportsCar cannot be found and ignore case).
Type t = Type.GetType("CarLibrary.SportsCar", false, true);
In the previous example, notice that the string you are passing into GetType() makes no mention of the assembly containing the type. In this case, the assumption is that the type is defined within the currently executing assembly. However, when you wish to obtain metadata for a type within an external private assembly, the string parameter is formatted using the type’s fully qualified name, followed by the friendly name of the assembly containing the type (each of which is separated by a comma):
// Obtain type information for a type within an external assembly.
Type t = null;
t = Type.GetType("CarLibrary.SportsCar, CarLibrary");
As well, do know that the string passed into Type.GetType() may specify a plus token (+) to denote a nested type. Assume you wish to obtain type information for an enumeration (SpyOptions) nested within a class named JamesBondCar. To do so, you would write the following:
//Obtain type information for a nested enumeration
//within the current assembly.
Type t =
Type.GetType("CarLibrary.JamesBondCar+SpyOptions");

398 C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G
Obtaining a Type Reference Using typeof()
The final way to obtain type information is using the C# typeof operator:
// Get the Type using typeof.
Type t = typeof(SportsCar);
Like Type.GetType(), the typeof operator is helpful in that you do not need to first create an object instance to extract type information. However, your code base must still have compile-time knowledge of the type you are interested in examining.
Building a Custom Metadata Viewer
To illustrate the basic process of reflection (and the usefulness of System.Type), let’s create a console application named MyTypeViewer. This program will display details of the methods, properties, fields, and supported interfaces (in addition to some other points of interest) for any type within mscorlib.dll (recall all .NET applications have automatic access to this core framework class library) or a type within MyTypeViewer itself.
Reflecting on Methods
The Program class will be updated to define a number of static methods, each of which takes a single System.Type parameter and returns void. First you have ListMethods(), which (as you might guess) prints the name of each method defined by the incoming type. Notice how Type.GetMethods() returns an array of System.Reflection.MethodInfo types:
// Display method names of type. public static void ListMethods(Type t)
{
Console.WriteLine("***** Methods *****");
MethodInfo[] mi = t.GetMethods(); foreach(MethodInfo m in mi)
Console.WriteLine("->{0}", m.Name); Console.WriteLine("");
}
Here, you are simply printing the name of the method using the MethodInfo.Name property. As you might guess, MethodInfo has many additional members that allow you to determine if the method is static, virtual, or abstract. As well, the MethodInfo type allows you to obtain the method’s return value and parameter set. You’ll spruce up the implementation of ListMethods() in just a bit.
Reflecting on Fields and Properties
The implementation of ListFields() is similar. The only notable difference is the call to Type.GetFields() and the resulting FieldInfo array. Again, to keep things simple, you are printing out only the name of each field.
// Display field names of type. public static void ListFields(Type t)
{
Console.WriteLine("***** Fields *****");
FieldInfo[] fi = t.GetFields(); foreach(FieldInfo field in fi)
Console.WriteLine("->{0}", field.Name); Console.WriteLine("");
}

C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G |
399 |
The logic to display a type’s properties is similar:
// Display property names of type. public static void ListProps(Type t)
{
Console.WriteLine("***** Properties *****");
PropertyInfo[] pi = t.GetProperties(); foreach(PropertyInfo prop in pi)
Console.WriteLine("->{0}", prop.Name); Console.WriteLine("");
}
Reflecting on Implemented Interfaces
Next, you will author a method named ListInterfaces() that will print out the names of any interfaces supported on the incoming type. The only point of interest here is that the call to GetInterfaces() returns an array of System.Types! This should make sense given that interfaces are, indeed, types:
// Display implemented interfaces. public static void ListInterfaces(Type t)
{
Console.WriteLine("***** Interfaces *****");
Type[] ifaces = t.GetInterfaces(); foreach(Type i in ifaces)
Console.WriteLine("->{0}", i.Name);
}
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.
public 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:
// Need to make use of the reflection namespace. using System;
using System.Reflection;
...
static void Main(string[] args)

400 C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G
{
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); ListFields(t);
ListProps(t);
ListMethods(t);
ListInterfaces(t);
}
catch
{
Console.WriteLine("Sorry, can't find type");
}
} while (!userIsDone);
}
At this point, MyTypeViewer.exe is ready to take out for a test drive. For example, run your application and enter the following fully qualified names (be aware that the manner in which you invoked Type.GetType() requires case-sensitive string names):
•System.Int32
•System.Collections.ArrayList
•System.Threading.Thread
•System.Void
•System.IO.BinaryWriter
•System.Math
•System.Console
•MyTypeViewer.Program
Figure 12-2 shows the partial output when specifying System.Math.

C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G |
401 |
Figure 12-2. Reflecting on System.Math
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:
public 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 12-3 shows the method metadata of the System.Globalization. GregorianCalendar type.

402 C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G
Figure 12-3. Method details of System.Globalization.GregorianCalendar
Interesting stuff, huh? Clearly the System.Reflection namespace and System.Type class allow you to reflect over many other aspects of a type beyond what MyTypeViewer is currently displaying. As you would hope, you can obtain a type’s events, discover which interfaces have been implemented explicitly, get the list of any generic parameters for a given member, and glean dozens of other details.
Nevertheless, at this point you have created an (somewhat capable) object browser. The major limitation, of course, is that you have no way to reflect beyond the current assembly (MyTypeViewer) or the always accessible mscorlib.dll. This begs the question, “How can I build applications that can load (and reflect over) assemblies not known at compile time?”
■Source Code The MyTypeViewer project can be found under the Chapter 12 subdirectory.
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. While this is all well and good, 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 clientside *.config file.
To illustrate dynamic loading, create a brand-new console application named ExternalAssemblyReflector. 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.

C H A P T E R 1 2 ■ T Y P E R E F L E C T I O N, L AT E B I N D I N G, A N D AT T R I B U T E - B A S E D P R O G R A M M I N G |
403 |
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();
//Does user want to quit? if (asmName.ToUpper() == "Q")
{
userIsDone = true; break;
}
//Try to load assembly.
try
{
asm = Assembly.Load(asmName); DisplayTypesInAsm(asm);
}
catch
{
Console.WriteLine("Sorry, can't find assembly.");
}
} while (!userIsDone);
}
}
}
Notice that the static Assembly.Load() method has been passed only the friendly name
of the assembly you are interested in loading into memory. Thus, if you wish to reflect over CarLibrary.dll, you will need to copy the CarLibrary.dll binary to the \Bin\Debug directory of the ExternalAssemblyReflector application to run this program. Once you do, you will find output similar to Figure 12-4.