Скачиваний:
64
Добавлен:
15.03.2015
Размер:
4.31 Mб
Скачать

588

Extreme C#

PART IV

Tip

The Assembly type has a GetModules() method that will get an array of modules within an assembly. From each of the modules, it’s possible to use GetTypes() to get an array of types to work with. As a shortcut, Listing 28.2 uses the GetTypes() method of the Assembly type to get all types belonging to all modules within that assembly.

Within the foreach loop, each type is extracted and printed. The types are obtained with a Get<X>() method, and the result is an <X>info object where <X> is one of the following type members:

Constructor

Field

Method

Property (including indexer)

Event

Each type member is printed to the console with the ToString() method, but this isn’t the only thing that can be done with each member. Each <X>Info class includes numerous methods and properties that can be invoked to obtain information. A good source of information on available methods and properties is the .NET SDK Frameworks documentation. Listing 28.3 shows how to compile the programs in Listings 28.1 and 28.2.

LISTING 28.3 Compile Instructions for Listings 28.1 and 28.2

csc Reflecting.cs Reflected.cs

Dynamically Activating Code

Dynamic code activation is the capability to make a runtime determination of what code will be executed. This capability can be useful in any situation where a late-bound framework is required.

Consider the Simple Object Access Protocol (SOAP) specification, which is transport protocol independent. Although SOAP is widely used with the HTTP protocol, the specification itself was constructed to allow implementation over other protocols, such as Simple Message Transport Protocol (SMTP). With an appropriate interface, Dynamic Link Libraries (DLL) could be constructed to separate the SOAP implementation from

Reflection

CHAPTER 28

589

the underlying protocol. Furthermore, with late-bound implementation, new protocols with the proper interface, packaged in their own DLLs, could be added to the framework at any time, without recompilation of the code. The late-bound capabilities of reflection could enable this scenario by assisting in the runtime determination of what transport protocol would be used for SOAP packages.

Examples in this chapter do not attempt to be this ambitious. However, Listing 28.4 shows how to perform a late-bound operation by dynamically activating the code in a specified assembly during runtime.

LISTING 28.4 Dynamically Activating Code: Reflecting.cs

using System;

using System.Reflection;

///<summary>

///Dynamically Activating Code.

///</summary>

class Reflecting

{

static void Main(string[] args)

{

Reflecting reflect = new Reflecting();

Assembly myAssembly

= Assembly.LoadFrom(“Reflecting.exe”);

reflect.DynamicallyInvokeMembers(myAssembly);

}

void DynamicallyInvokeMembers(Assembly myAssembly)

{

Type classType = myAssembly.GetType(“Reflected”);

PropertyInfo myProperty

= classType.GetProperty(“MyProperty”);

MethodInfo propGet = myProperty.GetGetMethod();

object reflectedObject

= Activator.CreateInstance(classType);

propGet.Invoke(reflectedObject, null);

MethodInfo myMethod

= classType.GetMethod(“MyInstanceMethod”);

myMethod.Invoke(reflectedObject, null);

}

}

28

EFLECTIONR

590

Extreme C#

PART IV

And the output is:

Invoking Static MyMethod.

Invoking Instance MyMethod.

The Main() method of Listing 28.4 gets an Assembly object with the static

Assembly.LoadFrom() method. The DynamicallyInvokeMembers() method uses the

Assembly object to get the Type object from the Reflected class. The Type object is then used to obtain the MyProperty property. Next, a MethodInfo object is obtained by calling the GetGetMethod() of the PropertyInfo object. The GetGetMethod() retrieves a copy of a property’s get method, which is, for reflection purposes, treated just like a method.

Note

Indexer get and set accessors are obtained just like property get and set accessors, with GetGetMethod() and GetSetMethod() calls.

The Reflected class is instantiated by using the Activator.CreateInstance() method. The instantiated object is then used as the first parameter in the Invoke() method of the MethodInfo object. This identifies which object to invoke the method on. The Invoke() method’s second parameter is the parameter list to send to the method, which would be an array of objects if there were parameters. In this case there are no parameters to send to the method, so the Invoke() method’s second parameter is set to null.

The next two lines show how to dynamically invoke an instance method. The syntax is the same as just explained for the property get accessor. However, the intermediate step, used in properties, isn’t necessary, and the method can be obtained directly with the GetMethod() method of the Type object.

The code in Listing 28.4 can be combined with Listing 28.1 to create an executable. Listing 28.5 shows how to compile them.

LISTING 28.5 Compile Instructions for Listings 28.1 and 28.4

csc Reflecting.cs Reflected.cs

Reflection.Emit

The Reflection.Emit API provides a means to dynamically create new assemblies. Using customized builders and generating Microsoft Intermediate Language (MSIL) or Common Intermediate Language (CIL) code enables programs to create new programs at

Reflection

CHAPTER 28

591

runtime. These assemblies may be dynamically invoked or saved to file where they may be reloaded and invoked or used by other programs.

Dynamic assembly creation can be useful for back-ends to compilers or scripting engines on tools such as Web browsers. Using the Reflection.Emit API, any tool can be extended to dynamically support .NET or any other Common Language Infrastructure (CLI) compliant system. Listing 28.6 shows how to both generate a dynamic assembly and save it as a console program.

LISTING 28.6 Dynamic Assembly Generation

using System;

using System.Reflection; using System.Reflection.Emit;

///<summary>

///Reflection Emit.

///</summary>

class Emit

{

static void Main(string[] args)

{

AppDomain myAppDomain = AppDomain.CurrentDomain;

AssemblyName myAssemblyName = new AssemblyName(); myAssemblyName.Name = “DynamicAssembly”;

AssemblyBuilder myAssemblyBuilder = myAppDomain.DefineDynamicAssembly( myAssemblyName, AssemblyBuilderAccess.RunAndSave);

ModuleBuilder myModuleBuilder = myAssemblyBuilder.DefineDynamicModule( “DynamicModule”,

“emitter.netmodule”);

TypeBuilder myTypeBuilder = myModuleBuilder.DefineType( “EmitTestClass”);

MethodBuilder myMethodBuilder = myTypeBuilder.DefineMethod( “Main”,

MethodAttributes.Public|MethodAttributes.Static,

null,

null);

28

EFLECTIONR

592

Extreme C#

PART IV

LISTING 28.6 continued

ILGenerator myILGenerator

= myMethodBuilder.GetILGenerator(); myILGenerator.EmitWriteLine(

“\n\tI must emit, reflection is pretty cool!\n”); myILGenerator.Emit(OpCodes.Ret);

Type myType = myTypeBuilder.CreateType(); object myObjectInstance

= Activator.CreateInstance(myType);

Console.WriteLine(“\nDynamic Invocation:”);

MethodInfo myMethod = myType.GetMethod(“Main”); myMethod.Invoke(myObjectInstance, null);

myAssemblyBuilder.SetEntryPoint(myMethod);

myAssemblyBuilder.Save(“emitter.exe”);

}

}

Before walking through the code in Listing 28.6, you may want to refer to Figure 28.1, which shows a model of the relationships between Reflection API components. The figure may make it clearer as to why each step is necessary.

New assemblies must be created in a specific AppDomain. Invocation of members belonging to a Type within an assembly must be done in the current AppDomain. Therefore, when this program begins, it gets a new AppDomain object by calling the

CurrentDomain() method of the AppDomain class.

To create the entire assembly in Listing 28.6, several steps are required:

1.Create an AssemblyBuilder.

2.Create a ModuleBuilder.

3.Create a TypeBuilder.

4.Create a MethodBuilder.

5.Generate IL.

6.Invoke members or persist assembly.

Each builder is created using a defining method of its parent in the hierarchy. This is another reason why the AppDomain object is required, to get an AssemblyBuilder.

Reflection

CHAPTER 28

593

The AssemblyBuilder object is created by calling the DefineDynamicAssembly() method of the AppDomain object. The parameters passed to DefineDynamicAssembly() are an AssemblyName object and an AssemblyBuilderAccess enum. The AssemblyBuilderAccess enum has three members: Run, RunAndSave, and Save. Run means the assembly can only be invoked in memory; Save means that the assembly can only be persisted (saved) to file; and RunAndSave means both Run and Save.

With an AssemblyBuilder object, a ModuleBuilder object is created. The parameters of the DefineDynamicModule() method are a string with name for the module and another string with the filename the module will be saved as. The example shows that the module filename will be “emitter.netmodule”.

Tip

The DefineDynamicModule() method has four overloads: two are for run-only modules and the other two are for run and persist modules. To guarantee that a module is included during persistence of an assembly, ensure one of the overloads with the filename parameter of the DefineDynamicAssembly() method is used.

TypeBuilder objects are created with the DefineType() method of the ModuleBuilder object. The DefineType() method takes a single string parameter with the name of the

Type.

The final builder object in Listing 28.6 is the MethodBuilder, which is created using the

DefineMethod() method of the TypeBuilder object. DefineMethod() has four parameters: name, method attributes, return type, and parameter types.

The name parameter is a string with the name of the method. In this case, it’s the Main() method. Since a Main() method must be defined as public and static, the second parameter uses the Public and Static members of the MethodAttributes enum. The return type is null, which defaults to void, and the parameter types is also null which means that this method does not accept arguments. When a method accepts arguments, the fourth parameter would be an array with the type definitions of each method parameter.

Next, the code is generated. To accomplish this, invoke the MethodBuilder object’s GetILGenerator() method. This results in an ILGenerator class that is used to create code.

This is a very simple method that writes a line of text to the console and returns. The EmitWriteLine() and Emit() methods perform this task.

28

EFLECTIONR