Reflection


An assembly is a piñata stuffed with goodies such as type information, MSIL code, and custom attributes. You use reflection to break open the assembly piñata to examine the contents. Reflection adds important features to .NET, such as metadata inspection, run-time creation of types, late binding, MSIL extraction, self-generating code, and so much more. These features are crucial to solving complex real-world problems that developers face every day.

The Reflection namespace is the container of all things related to reflection. Assembly, Module, LocalVariableInfo, MemberInfo, MethodInfo, FieldInfo, and Binder are some of the important members of the Reflection namespace. Reflection exposes several predefined customer attributes, such as AssemblyVersionAttribute, AssemblyKeyFileAttribute, and AssemblyDelaySignAttribute. The Reflection namespace contains other reflection-related namespaces; most notably the Reflection.Emit nested namespace. Reflection.Emit is a toolbox filled with tools for building assemblies, classes, and methods at run time, including the ability to emit metadata and MSIL code. Reflection.Emit is reviewed in Chapter 11.

The central component of reflection is the Type object. Its interface can be used to interrogate a reference or value type. This includes browsing methods, fields, parameters, and custom attributes. General information pertaining to the type is also available via reflection, including identifying the hosting assembly. Beyond browsing, Type objects support more intrusive operations. You can create instances of classes at run time and perform late binding of methods.

Obtaining a Type Object

The Object.GetType method, the typeof operator, and various methods of the Assembly object return a Type object. GetType is a member of the Object class, which is the ubiquitous base class. Therefore, GetType is inherited by .NET types and available to all managed instances. Each instance can call GetType to return the related Type object. The typeof operator extracts a Type object directly from a reference or value type. Assembly objects have several members that return one or more Type objects. For example, the Assembly.GetTypes method enumerates and returns the Types of the target assembly.

As a member base class object, GetType is accessible to all instances of reference and values types. GetType returns the Type object of the instance. This is the syntax of the GetType method:

 Type Type.GetType method: Type GetType() 

The following code creates instances of a value and a reference type, which are passed individually as object parameters to successive calls to the DisplayType method, which homogenizes the objects, where each object loses its distinction. The function extracts the type of the instance and displays the Type name. Finally, if the Type represents a ZClass, the ZClass.Display method is called:

 using System; namespace Donis.CSharpBook{     class Starter{         static void Main() {             int localvalue=5;             ZClass objZ=new ZClass();             DisplayType(localvalue);             DisplayType(objZ);         }         static void DisplayType(object parameterObject) {             Type parameterType=parameterObject.GetType();             string name=parameterType.Name;             Console.WriteLine("Type is "+name);             if(name == "ZClass") {                 ((ZClass) parameterObject).Display();             }         }     }     class ZClass {         public void Display() {             Console.WriteLine("ZClass::Display");         }     } } 

The typeof operator returns a Type object from a type. The typeof operator is evaluated at compile time, whereas the GetType method is invoked at run time. For this reason, the typeof operator is quicker but less flexible than the GetType method:

 typeof operator: typeof(type) 

An assembly is typically the host of multiple types. Assembly.GetTypes enumerates the types defined in an assembly. Assembly.GetType is overloaded four times. The zero-argument method returns the Type object of the Assembly and essentially the GetType method derived from the Object class. The one-argument version, in which the parameter is a string, returns a specific type defined in the assembly. The final two versions of GetType are an extension of the one-argument overloaded method. The two-argument method also has a Boolean parameter. When true, the method throws an exception if the type is not located. The three-argument version has a second Boolean parameter that stipulates case sensitivity. If this parameter is false, the case of the type name is significant.

Here are the methods:

 Assembly.GetTypes method: Type [] GetTypes() Assembly.GetType method: Type GetType() Type GetType(string typename) Type GetType(string typename, bool throwError) Type GetType(string typename, bool throwError, bool ignoreCase) 

An assembly can be diagrammed through reflection. The result is called the Reflection tree of that assembly. Each element of reflection, such as an AssemblyInfo, Type, MethodInfo, and ParameterInfo component, is placed on the tree. AppDomain is the root of the tree; GetAssemblies expands the tree from the root. The Reflection tree is a logical, not a physical representation. Assembly, Type, and ParameterInfo are some of the branches on the Reflection tree. You can explore the Reflection tree while inspecting the metadata of the application by using enumeration. For example, Assembly.GetCurrentAssembly returns the current assembly. Assembly.GetTypes will enumerate the types defined in the assembly. Type.GetMethods will enumerate the methods of each type. This process can continue until the application is fully reflected.

image from book
Figure 10-3: Diagram of the Reflection tree

Loading Assemblies

Assemblies near the root of the reflection tree can be loaded at run time using Assembly.Load and Assembly.LoadFrom. Assembly.Load uses the assembly loader to locate and bind to the correct assembly. Assembly.LoadFrom consults the assembly resolver to locate an assembly, which uses a combination of the strong name identifier and probing to bind and then load an assembly. The strong name includes the simple name, version, culture, and public key token of the assembly. Probing is the algorithm for locating an assembly. Both Assembly.Load and Assembly.LoadFrom are static methods that are overloaded several times. This is the core syntax:

 Assembly.Load method: static Assembly Assembly Load(string assemblyName) Assembly.LoadFrom method: static Assembly Assembly.LoadFrom(string AssemblyFileName) 

When the chosen assembly isn't found, Assembly.Load and Assembly.LoadFrom will fail. The Assembly Binding Log Viewer Tool (fuslogvw.exe), included in the .NET Framework SDK, is a useful tool for diagnosing probing failures because it displays information on the binding error.

The following sample code accentuates the difference between Assembly.Load and Assembly.LoadFrom:

 using System; using System.Reflection; namespace Donis.CSharpBook{   class Starter{     static void Main(){       Assembly library=Assembly.Load("library, Version=2.0.0.0, "+         "Culture=Neutral, PublicKeyToken=9b184fc90fb9648d");       Console.WriteLine("Assembly.Load:  {0}", library.FullName);       library=Assembly.LoadFrom("library.dll");       Console.WriteLine("Assembly.LoadFrom {0}", library.FullName);     }   } } 

Assembly.Load and Assembly.LoadFrom reference external assemblies. How about referencing the currently executing assembly? Assembly.GetExecutingAssembly is a static method and returns a reference to the currently executing assembly. This is valuable for interrogating the metadata or MSIL of the running assembly.

 Assembly.GetExecutingAssembly method: static Assembly Assembly.GetExecutingAssembly() 

Both Assembly.Load and Assembly.LoadFrom return a reference to an assembly. That assembly can be reflected, code loaded, and then executed. Assembly.ReflectionOnlyLoad and Assembly.ReflectionOnlyLoadFrom load an assembly only for reflection, so the code cannot be executed later. Although you could reflect a type and iterate all the methods, you could not invoke a method of that type. To confirm the way an Assembly is loaded, use the ReflectionOnly property. ReflectionOnly is a Boolean attribute of an Assembly and is true if an Assembly is loaded for reflection only. Assembly.ReflectionOnlyLoad and Assembly.ReflectionOnlyLoadFrom are equivalent to Assembly.Load and Assembly.LoadFrom, respectively, without the execute capability. ReflectionOnly yields performance benefits.

The following program benchmarks the performance of Assembly.Load versus Assembly.ReflectionOnlyLoad. The DateTime function has poor resolution and is inferior for most testing scenarios. Instead, a high-performance timer is needed to obtain added resolution. The QueryPerformanceCounter API returns a high-performance counter accurate to a nanosecond. Prior to .NET Framework 2.0, QueryPerformanceCounter was available only through interoperability. The Stopwatch class, which is a thin wrapper for the QueryPerformanceCounter and related APIs, was introduced in .NET Framework 2.0 and found in the System.Diagnostics namespace:

 using System; using System.Reflection; using System.Diagnostics; namespace Donis.CSharpBook{   class OnlyLoad{     static void Main() {       Stopwatch duration=new Stopwatch();       duration.Reset();       duration.Start();       Assembly a=Assembly.Load("library");       duration.Stop();       Console.WriteLine(duration.ElapsedTicks.ToString());       duration.Reset();       duration.Start();       a=Assembly.ReflectionOnlyLoad("library");       duration.Stop();       Console.WriteLine(duration.ElapsedTicks.ToString());      }    } } 

Execution time of Assembly.Load versus Assembly.ReflectionOnlyLoad might vary between different implementations of the CLI and other factors. The program compares ticks, which is an abstraction of time. Running the problem several times indicates that Assembly.ReflectionOnlyLoad is about 29 percent faster than Assembly.Load. This is only an approximation.

Type.ReflectionOnlyGetType combines the functionality of Assembly.ReflectionOnlyLoad and the typeof operator. The named assembly is loaded for inspection only, and a Type object is returned to the specified type. Because the assembly is opened only for browsing, you cannot create an instance of the type or invoke a method on the type. ReflectionOnlyGetType is a static method of the Type class and loads a Type for inspection only. The method returns a Type object:

 Type.ReflectionOnlyType method: static Type ReflectionOnlyType(string typeName, bool notFoundException,   bool ignoreCase) 

The typeName parameter is a combination of the Assembly and Type names. The Assembly and Type names are comma-delimited. To raise an exception if the type is not found, set the notFoundException parameter to true. When false, the ignoreCase parameter indicates that the Type name is case sensitive:

 using System; using System.Reflection; namespace Donis.CSharpBook{   class ReflectOnlyType{     static void Main() {       Type zType=Type.ReflectionOnlyGetType("Donis.CSharpBook.ClassA,          Library", false, false);       Console.WriteLine(zType.Name);      }   } } 

Browsing Type Information

Inspecting a type begins with the Type object. Reflection has a straightforward interface for mining the metadata of a type. Inspecting the metadata of a type essentially consists of spanning a series of collections. The Type object interface publishes several methods and properties related to reflection.

Type.GetMembers returns the ultimate collection: the collection of all members. All members, whether the member is a method, field, event, or property, are included in the collection. GetMembers returns a MemberInfo array that contains an item for each member. GetMember returns a single MemberInfo object for the named member. MemberInfo.MemberType is a property of the MemberInfo.MemberTypes type, which is a bitwise enumeration distinguishing a member as a method, field, property, event, constructor, or something else. (See Table 10-6.) MemberInfo has relatively few properties and operations. Here are some of the more useful. The MemberInfo.Name offers the name of the type member. MemberInfo.MetadataToken, another property, returns the metadata token of the member. MemberInfo.ReflectedType provides the Type object from which the MemberInfo object was extracted.

Table 10-6: MemberTypes Enumeration

MemberType

Value

MemberTypes.Constructor

0x01

MermberTypes.Custom

0x40

MemberTypes.Event

0x02

MemberTypes.Field

0x04

MemberTypes.Method

0x08

MemberTypes.NestedType

0x80

MemberTypes.Property

0x10

MemberTypes.TypeInfo

0x20

MemberTypes.All

0xBF

Type.GetMembers creates a basket that contains all the members of the reflected type. You can be somewhat more granular. Type.GetMethods or Type.GetMethod returns a collection of methods or a specific method. Type.GetFields or Type.GetField similarly returns a collection of fields or a specific field. Table 10-7 lists the methods that return specific collections where the nonplural method returns a specific member of that collection.

Table 10-7: Type Methods That Return Metadata Collections

Method

Returns

Type of Member

GetConstructors

ConstructorInfo []

Constructor

GetCustomAttributes

Object []

Custom attribute

GetDefaultMembers

MemberInfo []

Default member

GetEvents

EventInfo []

Event

GetFields

FieldInfo []

Field

GetInterfaces

Type []

Implemented interface

GetMembers

MemberInfo []

All members

GetMethods

MethodInfo []

Method

GetNestedTypes

Type []

Nested Type

GetProperties

PropertyInfo []

Property

The Type.GetMethods that return MethodInfo arrays are overloaded to be called with no parameters or with a single parameter, which is BindingFlags:

 Type.GetMethods method: MethodInfo [] GetMethods(); MethodInfo[] GetMethods(BindingFlags binding) 

BindingFlags is a bitwise enumeration that filters the results of a collection. For example, to include private members in a collection stipulates the BindingFlags.NonPublic flag. Some BindingFlags, such as InvokeMember, are not applicable in this context. When stipulating BindingFlags, there are no default flags. If you specify one flag, you must specify every required flag. Each inclusion must be specified explicitly. The zero-argument version of GetMethods grants default bindings, which vary depending on the method. At a minimum, most of the methods default to BindingFlags.Public and BindingFlags.Instance. Notably, the BindingFlags.Static flag is not always defaulted and static members are often excluded from a collection. The following code first iterates private instance (nonpublic) members. Afterward, static public members are iterated.

 using System; using System.Reflection; namespace Donis.CSharpBook{   class DumpType{     public static void Main() {       ZClass zObj=new ZClass();       Type tObj=zObj.GetType();       MemberInfo [] members=tObj.GetMembers(         BindingFlags.Instance|         BindingFlags.NonPublic);       foreach(MemberInfo member in members) {         Console.WriteLine(member.Name);       }       members=tObj.GetMembers(         BindingFlags.Public|BindingFlags.Static);   Console.WriteLine(" ");     foreach(MemberInfo member in members) {         Console.WriteLine(member.Name);       }     }   }   class ZClass {     private int vara=5;     public int PropA {       get {         return vara;       }     }     static public void MethodA() {       Console.WriteLine("ZClass::MethodA called.");     }   } } 

The following application calls DumpMethods to dump the public methods of a class. This code demonstrates various aspects of Reflection.

 using System; using System.Reflection; namespace Donis.CSharpBook{     class DumpType{         static void Main(string [] argv) {             targetType=LoadAssembly(argv[0], argv[1]);             DumpReportHeader();             DumpMethods();          }         static public Type LoadAssembly(string t, string a) {             return Type.ReflectionOnlyGetType(t+","+a, false, true);         }         static void DumpReportHeader() {             Console.WriteLine("\n{0} type of {1} assembly",                 targetType.Name, targetType.Assembly.GetName().Name);             Console.WriteLine("\n{0,22}\n", "[ METHODS ]");        }         static void DumpMethods() {             string dashes=new string('-', 50);             foreach(MethodInfo method in targetType.GetMethods()) {                 Console.WriteLine("{0,12}{1,-12}", " ", method.Name+" "+                      "<"+method.ReturnParameter.ParameterType.Name+">");                 int count=1;                 foreach(ParameterInfo parameter in method.GetParameters()){                     Console.WriteLine("{0, 35}{1, -12}",                         " ", (count++).ToString()+" "+ parameter.Name+                         " ("+parameter.ParameterType.Name+")");                 }                 Console.WriteLine("{0,12}{1}", " ", dashes);             }         }         private static Type targetType;     } } 

In the preceding code, a type name and an assembly name are read from the command line. The type to be dumped is argv[0], while the assembly hosting the type is argv[1]. With this information, the LoadAssembly method employs Type.ReflectionOnlyGetType and loads the type for inspection only. The DumpMethods function iterates the methods of the target type, and then iterates the parameters of each method. The name of each method and parameter is displayed. This dumps the members of the Console class:

 dumpmethods System.Console mscorlib.dll 

Dynamic Invocation

Methods can be dynamically invoked at run time using reflection. The benefits and perils of early binding versus late binding were reviewed in the discussion on delegates earlier in the book.

In dynamic binding, you build a method signature at run time and then invoke the method. This is somewhat later than late binding with delegates. When compared with delegates, dynamic binding is more flexible, but is regrettably slower. At compile time, the method signature must match the delegate at the site of the run-time binding. Dynamic binding removes this limitation, and any method can be invoked at the call site, regardless of the signature. This is more flexible and extensible than run-time binding.

In reflection, there are two approaches to invoking a method dynamically: MethodInfo.Invoke and Type.InvokeMember, where MethodInfo.Invoke is the simplest solution. However, Type.InvokeMember is more malleable. The basic syntax of MethodInvoke requires only two parameters: an instance of a type and an array of parameters. The method is bound to the instance provided. If the method is static, the instance parameter is null. To avoid an exception at run time, which is never fun, care must be taken to ensure that the instance and parameters given to MethodInfo.Invoke match the signature of the function.

This is the MethodInfo.Invoke syntax:

 object Invoke1(object obj, object [] arguments) object Invoke2 (object obj, BindingFlags flags, Binder binderObj,      object [] arguments, CultureInfo culture) 

The second Invoke method has several additional parameters. The obj parameter is the instance that method is bound. If invoking a static method, the obj parameter should be null but is otherwise ignored. BindingFlags are next and further describe the Invoke operation, such as Binding.InvokeMethod. When no binding flags are specified, the default is BindingFlags.DefaultBinding. Binderobj is used to select the appropriate candidate among overloaded methods. Arguments is the array of method arguments as defined by the method signature. The culture argument sets the culture, which defaults to the culture of the system. Return is the method return or null for a void return.

Alternatively, you can invoke a method dynamically at run time using Type.InvokeMember, which is overloaded several times.

This is the Type.InvokeMember syntax:

 object InvokeMember1(string methodName, BindingFlags flags,      Binder binderObj, object typeInstance, object [] arguments) object InvokeMember2(string methodName, BindingFlags flags,      Binder binderObj, object typeInstance, object [] arguments,      CultureInfo culture) object InvokeMember3(string methodName, BindingFlags flags,      Binder binderObj, object typeInstance, object [] arguments,      ParameterModifier [] modifiers, CultureInfo culture,      string [] namedParameters) 

InvokeMember1 is the core overloaded method, and it has several parameters. The methodName parameter is the name of the method to invoke. The next parameter is BindingFlags. Binderobj is the binder used to discriminate between overloaded methods. The object to bind the method again is typeInstance. arguments is the array of method parameters. InvokeMember2 adds an additional parameter. To set the culture, use the culture parameter. InvokeMember3 is the final overload with one additional parameter: namedParameters, which is used to specify named parameters.

In the following code, dynamic invocation is demonstrated with MethodInfo.Invoke and Type.InvokeMember:

 using System; using System.Reflection; namespace Donis.CSharpBook{     class Starter{         static void Main(){             ZClass obj=new ZClass();             Type tObj=obj.GetType();             MethodInfo method=tObj.GetMethod("MethodA");             method.Invoke(obj, null);             tObj.InvokeMember("MethodA", BindingFlags.InvokeMethod,                 null, obj, null);         }     }     class ZClass {         public void MethodA() {             Console.WriteLine("ZClass.Method invoked");         }     } } 

Binders

Members such as methods can be overloaded. In reflection, binders determine the specific method to invoke from a list of possible candidates. The default binder selects the best match based on the number and type of arguments. You can provide a custom binder and choose a specific overloaded member. Both MethodInfo.Invoke and Type.InvokeMember offer a binder argument for this reason.

The Binder class is an abstract class; as such, it is implemented through a derived concrete class. Binder has abstracted methods to select a field, property, and method from available overloaded candidates. Binder is a member of the Reflection namespace. Table 10-8 lists the public members of the Binder class. Each method included in the table is abstract and must be overridden in any derivation.

Table 10-8: Abstract Methods of the Binder Class

Binder Method

Description

BindToField

Selects a field from a set of overloaded fields

BindToMethod

Selects a method from a set of overloaded methods

ChangeType

Coerces the type of an object

ReorderArgumentArray

Resets the argument array; associated with the state parameter of BindToMethod member

SelectMethod

Selects a method from candidate methods

SelectProperty

Selects a property from candidate properties

Binder.BindToMethod is called when a method is invoked dynamically. To override the default selection criteria of overloaded methods, create a custom binder class. Inherit the binder class and at least minimally override and implement each abstract method of the base class. How the binder is used determines the methods to fully implement. If the MethodInfo.Invoke and Type.InvokeMember methods are called with the BindingFlags.InvokeMethod flag set, BindToMethod will be invoked and should be completely implemented.

The Binder.BindToMethod syntax is as follows:

 public abstract BindToMethod(BindingFlags flags, MethodBase [] match,   ref object [] args, ParameterModifier [] modifiers, CultureInfo culture,   string [] names, out object state) 

The first parameter of BindToMethod is BindingFlags, which is the usual assortment of binding flags. match is a MethodBase array with an element for each possible candidate. If there are three candidates, match has three elements. At present, MethodBase-derived classes are limited to ConstructorInfo; MethodInfo.args are the values of method parameters. Modifiers is an array of ParameterModifier used with parameter signatures of modified types. Culture sets the culture. Names are the identifiers of methods included as candidates. The modifiers, culture, and names parameters can be null. The final parameter, state, is used with parameter reordering. If this parameter is non-null, Binder.ReorderArgumentArray is called after BindToMethod and returns the parameters to the original order.

There is no prescription for selecting a method from a set of candidates. You are free to be creative and employ whatever logic seems reasonable. Here is a partially implemented but workable custom Binder class. BindToMethod is implemented but the other methods are essentially stubbed. This code is somewhat circumscribed and not written for general-purpose application.

 using System; using System.Reflection; using System.Globalization; class CustomBinder:Binder {     public override FieldInfo BindToField(BindingFlags bindingAttr,             FieldInfo[] match, object value, CultureInfo culture) {         return null;     }     public override MethodBase BindToMethod(BindingFlags bindingAttr,             MethodBase[] match, ref object[] args,             ParameterModifier[] modifiers, CultureInfo culture,             string[] names, out object state) {         Console.WriteLine("Overloaded Method:");         foreach(MethodInfo method in match) {             Console.Write("\n {0} (", method.Name);             foreach(ParameterInfo parameter in                    method.GetParameters()) {                 Console.Write(" "+parameter.ParameterType.ToString());             }             Console.WriteLine(")");         }         Console.WriteLine();         state=null;         if(long.Parse(args[0].ToString())>int.MaxValue) {             return match[0];         }         else {             return match[1];         }     }     public override object ChangeType(object value, Type type,             CultureInfo culture) {         return null;     }     public override void ReorderArgumentArray(ref object[] args,             object state){     }     public override MethodBase SelectMethod(BindingFlags bindingAttr,            MethodBase[] match, Type[] types,            ParameterModifier[] modifiers) {         return null;     }     public override PropertyInfo SelectProperty(BindingFlags bindingAttr,             PropertyInfo[] match, Type returnType, Type[] indexes,             ParameterModifier[] modifiers) {         return null;     } } class ZClass {     public void MethodA(long argument) {         Console.WriteLine("Long version: "+argument.ToString());     }     public void MethodA(int argument) {        Console.WriteLine("Int version: "+argument.ToString());     }     public void MethodA(int argument, int argument2) {         Console.WriteLine("ZClass::Method 2 arg");     } } class Starter {     public static void Main() {    ZClass obj=new ZClass();    Type tObj=obj.GetType();         CustomBinder theBinder=new CustomBinder();         tObj.InvokeMember("MethodA", BindingFlags.InvokeMethod,             theBinder, obj, new Object[] {int.MinValue});         Console.WriteLine();         tObj.InvokeMember("MethodA", BindingFlags.InvokeMethod, theBinder,             obj, new Object[] {long.MaxValue});     } } 

In the preceding code, CustomBinder inherits from the Binder class. As mentioned, BindToMethod is the sole method implemented in the code. This function lists the signatures of each candidate. The appropriate method is then chosen. The first foreach loop iterates the candidates while listing the method names. The inner foreach loop iterates and lists the parameters of each MethodInfo object. This version of BindToMethod is written specifically for the one-argument methods of the ZClass BindToMethod. The argument value is tested. If the value is within the range of a long, the first method is returned. This method has a long parameter. Otherwise, the second candidate, which has an integer parameter, is returned.

In Main, an instance of the ZClass and custom binder is created. ZClass is a simple class with an overloaded method. ZClass.MethodA is overloaded three times. The type object is then extracted from the ZClass instance, and Type.InvokeMember is called twice with the custom binder. InvokeMember is called to dynamically invoke "MethodA", first with an integer parameter and then with a long parameter. This is the output from the application:

 Overloaded Method: MethodA ( System.Int64 ) MethodA ( System.Int32 ) Int version: -2147483648 Overloaded Method: MethodA ( System.Int64 ) MethodA ( System.Int32 ) Long version: 9223372036854775807 

Type Creation

Until now, the emphasis of this chapter has been on reflecting existing objects. Object instances can also be created dynamically at run time. You can reflect, bind methods, and otherwise treat the dynamic object as a static object. As often with Reflection, the primary benefit is added flexibility. What if the type of an instance is not determinable at compile time? With Reflection, that decision can be delayed until run time, when the particular class can be chosen by the user, stipulated in a configuration file, or otherwise selected by some intermediary.

The Activator class is a member of the Reflection namespace and facilitates the creation of objects at run time. Activator consists of four static member methods: CreateInstance creates an instance of a type; CreateInstanceFrom leverages Assembly.LoadFrom to reference an assembly and then create an instance of a type found in the assembly; and CreateComInstanceFrom and GetObject instantiate a COM object and a proxy to a Remote object, respectively. To simply create an instance of a .NET type, call CreateInstance or CreateInstanceFrom. Both CreateInstance and CreateInstanceFrom are overloaded several times. This is the list of CreateInstance methods:

 Activator.CreateInstance syntax: static T CreateInstance<T>() static public ObjectHandle CreateInstance(ActivationContext context) static object CreateInstance(Type type) static public ObjectHandle CreateInstance(ActivationContext context, string [] customData) static ObjectHandle CreateInstance(string assemblyName, string typeName) static object CreateInstance(Type type, bool ctorPublic) static object CreateInstance(Type type, object [] ctorArgs) static ObjectHandle CreateInstance<T, U> (T, U) static ObjectHandle CreateInstance(string assemblyName, string typeName,   object [] activationAttributes) static object CreateInstance(string assemblyName, string TypeName,   object [] activationAttributes) static object CreateInstance(Type type, BindingFlags bindingAttr,   Binder binder, object [] args, CultureInfo culture) static object CreateInstance(Type type, object [] args, object [] activationAttributes) static Object CreateInstance(Type type, BindingFlags bindingAttr, Binder binder,   object [] args, CultureInfo culture, object [] activationAttributes) static ObjectHandle CreateInstance(string assemblyName, string typeName,   bool ignoreCase, BindingFlags bindingAttr, Binder Binder,   object [] args, CultureInfo culture, object [] activationAttributes,   Evidence securityInfo) 

Some CreateInstance methods—and all CreateInstanceFrom methods—return an ObjectHandle, which is usually returned when creating an instance of a type foreign to the current assembly. ObjectHandle is found in the System.Runtime.Remoting namespace. ObjectHandle.Unwrap unwraps the ObjectHandle to disclose a proxy to the remoted object. Alternatively, the AppDomain.CreateInstanceFromAndUnwrap method creates an instance of the type and returns the object unwrapped with one less step. Of course, this means that an additional step of obtaining an AppDomain object is necessary. However, if the AppDomain object is handy, CreateInstanceFromAndUnwrap is convenient.

The following code creates three instances of the same type. A local and two remote proxies to object instances are constructed:

 using System; using System.Reflection; using System.Runtime.Remoting; namespace Donis.CSharpBook{   class ZClass {     public void MethodA(DateTime dt) {        Console.WriteLine("MethodA invoked at "+          dt.ToLongTimeString());     }   }   class Starter{     static void Main() {       CreateLocal();       CreateRemote1();       CreateRemote2();     }     static void CreateLocal() {       object obj=Activator.CreateInstance(typeof(ZClass));       ((ZClass) obj).MethodA(DateTime.Now);     }     static void CreateRemote1() {       ObjectHandle hObj=Activator.CreateInstance("library",         "Donis.CSharpBook.ZClass");       object obj=hObj.Unwrap();       MethodInfo method=obj.GetType().GetMethod("MethodA");       method.Invoke(obj, new object [1] {DateTime.Now});     }     static void CreateRemote2() {       AppDomain domain=AppDomain.CurrentDomain;       object obj=domain.CreateInstanceFromAndUnwrap("library.dll",          "Donis.CSharpBook.ZClass");       MethodInfo method=obj.GetType().GetMethod("MethodA");       method.Invoke(obj, new object [1] {DateTime.Now});     }   } } 

The preceding code presents three vehicles for creating an instance of a type, binding a method to the type, and finally invoking that method dynamically. Dynamically invoking a method through casting is a mechanism not previously demonstrated. (Method.Invoke and Type.InvokeMember were reviewed earlier.)

The code for engaging a method through casting is listed as follows. Calling a method dynamically through casting has substantial performance gains when compared with MethodInfo.Invoke or Type.InvokeMember. (MethodInfo.Invoke and Type.InvokeMember were reviewed earlier in this chapter.)

 ((ZClass) obj).MethodA(DateTime.Now); 

Either directly or indirectly, the Activator.CreateInstance and CreateInstanceFrom methods return an object. As the preceding code demonstrates, you can cast the generic object to a specific type and invoke the chosen method. This combines late and early binding, which has significant performance benefits when compared with late binding the type and method.

Late Binding Delegates

A delegate is a repository of type-safe function pointers. A single-cast delegate holds one function pointer, whereas a multicast delegate is a basket of one or more delegates. Delegates are type-safe because the signatures of the delegate and function pointer must match. Normally a compile error is generated if there is a mismatch. Unlike MethodInfo, a function pointer is discriminatory and bound to a specific object. MethodInfo is nondiscriminatory and can be associated with any affiliated object. This is the reason why MethodInfo.Invoke and Type.InvokeMember methods have an object parameter to associate an instance with the target method. This section assumes a fundamental understanding of delegates. If you would like a review, take a look at Chapter 8, "Delegates and Events." The following code is typical of delegates:

 using System; using System.Reflection; namespace Donis.CSharpBook{   delegate void XDelegate(int arga, int argb);   class ZClass {     public void MethodA(int arga, int argb) {       Console.WriteLine("ZClass.MethodA called: {0} {1}", arga, argb);     }   }   class Starter{     static void Main(){       ZClass obj=new ZClass();       XDelegate delObj=new XDelegate(obj.MethodA);       delObj.Invoke(1,2);       delObj(3,4);     }   } } 

In this code, XDelegate is the delegate type. MethodA is then added to the delegate and invoked. First, invoke using the Delegate.Invoke method. Second, invoke the function through the delegate using the C# syntax. At compile time, XDelegate expands into a class derived from a Delegate type. The Invoke method is the most important member implemented and added to the class interface of the derived type. The signature of Invoke matches the signature of the delegate. Therefore, XDelegate.Invoke has two integer parameters and enforces type safeness of related function pointers.

The preceding code assumes the delegate signature is known at compile time. What if the signature of the delegate is not known at compile time? Because a delegate is an implied class that is similar to any class, you can create an instance of a delegate at run time, bind a method to the delegate, and invoke the method. Instead of invoking a member method, a function pointer is bound and executed against the delegate. The Delegate.CreateDelegate and Delegate.DynamicInvoke methods provide this behavior. Late binding is not as type-safe as compile-time type checking. (This also pertains to late binding of delegates.) Care must be taken to avoid run-time exceptions. As always, the seminal benefit of late binding is additional flexibility, but performance might suffer.

CreateDelegate constructs a new delegate at run time and then assigns a function pointer to the newly invented delegate. CreateDelegate is an overloaded method where the essential parameters are the delegate type and method identity. The delegateType is the type of delegate being created. Method is the initial function pointer being assigned to the delegate. The signature of the method represented by a MethodInfo object should match that of the delegate type. These are the overloaded methods:

The Delegate.CreateDelegate syntax is as follows:

 static Delegate CreateDelegate(Type type, MethodInfo method) static Delegate CreateDelegate(Type type, MethodInfo method, bool thrownOnBindFailure) static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method) static Delegate CreateDelegate(Type type, object target, string method) static Delegate CreateDelegate(Type type, Type target, string method) static Delegate CreateDelegate(Type type, object firstArgument, MethodInfo method, bool throwOnBindFailure) static Delegate CreateDelegate(Type type, object target, string method,   bool ignoreCase) static DelegateCreateDelegate(Type type, object firstArgument, Type target, string method) static Delegate CreateDelegate(Type type, Type target, string method, bool ignoreCase) static Delegate CreateDelegate(Type type, object target, string method, bool ignoreCase, bool throwOnBindFailure) static Delegate.CreateDelegate(Type type, object firstArgument, Type target, string method, bool ignoreCase) static Delegate.CreateDelegate(Type type, Type target, string method, bool ignoreCase, bool throwOnBindFailure) static Delegate.CreateDelegate(Type type, object firstArgument, Type target, string method, bool ignoreCase, bool throwOnBindFailure) 

After creating a delegate at run time, call DynamicInvoke to invoke any function pointers assigned to the delegate. The dynamically created delegate is deprived of the Invoke method and language-specific operations. Subsequently, you cannot call the Invoke method on a delegate returned from CreateDelegate. This is the major difference between compile-time and run-time instances of delegates. An array of function arguments is the only parameter of DynamicInvoke.

This is the DynamicInvoke syntax:

 object DynamicInvoke(object [] args) 

CreateDelegate and DynamicInvoke are demonstrated in the following code:

 using System; using System.Reflection; namespace Donis.CSharpBook{   delegate void theDelegate(int arga, int argb);   class ZClass {     public void MethodA(int arga, int argb) {       Console.WriteLine("ZClass.MethodA called: {0} {1}", arga, argb);     }   }   class Starter{     static void Main(){       Type tObj=typeof(System.MulticastDelegate);       ZClass obj=new ZClass();       Delegate del=Delegate.CreateDelegate(typeof(theDelegate), obj,         "MethodA");       del.DynamicInvoke(new object [] {1,2});     }   } } 

Function Call Performance

Several ways to invoke a method have been presented in this chapter—from a simple method call to the more complex dynamic invocation. Performance is an important criterion when evaluating competing methodologies. For example, a simple call bound at compile time should be quicker than a method bound at run time. Depending on the application, the differentiation might be material. Losing a few nanoseconds occasionally might not be conspicuous in a user interface—driven application. However, a few lost nanoseconds in a server application multiplied by thousands of users can pose a real problem.




Programming Microsoft Visual C# 2005(c) The Language
Microsoft Visual Basic 2005 BASICS
ISBN: 0619267208
EAN: 2147483647
Year: 2007
Pages: 161

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net