Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro CSharp And The .NET 2.0 Platform (2005) [eng]

.pdf
Скачиваний:
110
Добавлен:
16.08.2013
Размер:
10.35 Mб
Скачать

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.