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 many important features to .NET, such as metadata inspection, run-time creation of types, late binding, MSIL extraction, and self-generating code. These features are crucial to solving complex real-world problems that developers face every day.

The Reflection namespace is the container of most things related to reflection. Assembly, Module, LocalVariableInfo, MemberInfo, MethodInfo, FieldInfo, and Binder are some of the important types and members of the Reflection namespace. There are also some reflection-related attributes in the Reflection namespace, 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 14.

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

Obtaining a Type Object

There are several ways to obtain a Type object from an instance. 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. GetType is inherited by every .NET type. For that reason, you can call GetType on any managed object.

The typeof operator takes an object as a parameter and returns the related Type object.

Assemblies are natural boundaries for types. An Assembly object, which is a thin wrapper for an assembly, offers several member functions that return a Type object. For example, the Assembly.GetTypes method enumerates and returns all the Types of the current assembly.

GetType returns the Type object of the instance. Here is the signature of the GetType method:

Type GetType()

The following code creates a value and then a reference type, which are passed as parameters to successive calls to the DisplayType method. The parameter accepts an object type, which homogenizes the parameter. The function first obtains the Type object from the parameter. The type name is then displayed. Next, run-time type information (RTTI) is used. If the Type object is a ZClass, the ZClass.Display method is called on the parameter object. One of the advantages of reflection is making decisions at run time:

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 more efficient but less flexible than the GetType method. Here is the syntax for the typeof operator:

typeof(type)

An assembly typically contains multiple types. The Assembly.GetTypes method enumerates the types contained in an assembly. Use another method, Assembly.GetType, to return a Type object of a specific instance. Assembly.GetType is overloaded four times. The zero-argument version of GetType returns the Type of the current instance. The one-argument version has a string parameter. That parameter is the fully qualified name of the type to be returned. The final two versions of GetType are an extension of the one-argument version of the method. The two-argument method also has a Boolean parameter. When true, the method throws an exception if the type, which is defined in the string parameter, 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 in the string parameter is significant.

Here are the signatures for the methods:

Type[] GetTypes()

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. Figure 13-3 shows the reflection tree starting with an application domain (AppDomain). Each level of reflection, such as an assembly, a type, and a method, represents a different branch on the tree. AppDomain is the root of the tree; AppDomain.GetAssemblies expands the tree from the root. The reflection tree is a logical, not a physical representation. You can realize the reflection tree using reflection to enumerate the metadata of the application. For example, Assembly.GetCurrentAssembly returns the current assembly. With the assembly instance, Assembly.GetTypes enumerates the types defined in the assembly. For each type, Type.GetMethods will enumerate the methods of that type. This process continues until the application is fully reflected and the logical reflection tree is constructed.

Diagram of the reflection tree

Figure 13-3. Diagram of the reflection tree

Loading Assemblies

Assemblies reside near the root of the reflection tree and are loaded at run time using Assembly.Load and Assembly.LoadFrom. Assembly.Load uses the assembly loader to locate and bind an assembly. Assembly.LoadFrom consults the assembly resolver to locate and load the correct assembly. This method 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. For example, when loading an assembly, the current directory typically is probed first. Both Assembly.Load and Assembly.LoadFrom are static methods that are overloaded several times. Here is the signature of a couple of the overloaded methods:

static Assembly Load(string AssemblyName)

static Assembly LoadFrom(string AssemblyFile)

Assembly.Load and Assembly.LoadFrom fail when the target assembly is not found. When these functions fail, the Assembly Binding Log Viewer tool is helpful in diagnosing the problem. The Assembly Binding Log Viewer tool (fuslogvw.exe), included in the .NET Framework SDK, is a useful tool for diagnosing any probing failure.

The following sample code demonstrates 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 other 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, manifest, or MSIL of the running assembly. Here is the signature of the GetExecutingAssembly method:

static Assembly Assembly.GetExecutingAssembly()

Both Assembly.Load and Assembly.LoadFrom return a reference to an assembly. That assembly then can be reflected. The code for the assembly also can be loaded and then executed. Assembly.ReflectionOnlyLoad and Assembly.ReflectionOnlyLoadFrom load an assembly for reflection but not for execution. This means the code cannot be executed for the newly loaded assembly. In this circumstance, you can reflect a type and iterate all the methods. Yet the methods cannot be invoked. 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 assuming the code of the assembly does not need to be executed.

The following program provides performance benchmarks 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 required to obtain the needed resolution. The QueryPerformanceCounter API returns a high-performance counter accurate to a nanosecond. Prior to .NET Framework 2.0, QueryPerformanceCounter was available only through platform invoke interoperability (a managed-to-native call using PInvoke). The Stopwatch class, which is a thin wrapper for the QueryPerformanceCounter and related APIs, was introduced in .NET Framework 2.0 and is 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 Common Language Infrastructure (CLI) and other factors. The program compares ticks, which is an abstraction of time. Running the program several times indicates that Assembly.ReflectionOnlyLoad is about 29 percent faster than Assembly.Load. This data is only anecdotal, though.

Type.ReflectionOnlyGetType is a static method and combines the functionality of Assembly.ReflectionOnlyLoad and the typeof operator. The named assembly is loaded for inspection only, and a Type object is returned for the specified type that is found in that assembly. Because the assembly is opened only for inspection, you cannot create an instance of the type or invoke a method on that type. Here is the signature of the Type.ReflectionOnlyGetType method:

static Type ReflectionOnlyType(string typeName, bool notFoundException,
    bool ignoreCase)

The typeName parameter is a combination of the Assembly and the Type name. The Assembly and the Type name are comma-delimited. Null is returned if the assembly or the type is not located. To raise an exception if the assembly or type is not found, set the notFoundException parameter to true. Setting the ignoreCase parameter to false 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 obtaining a Type object. Reflection has a straightforward interface for examining the metadata of a Type object. The Type object public interface exposes several methods and properties related to reflection. Inspecting the metadata of a type essentially consists of spanning a series of collections.

Type.GetMembers returns a collection that includes all members of the current type. Every member, whether it is a method, field, event, or property, is included in the collection. GetMembers returns a MemberInfo array that contains an element for each member. Another method, 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 identifying a member as a method, field, property, event, constructor, or something else. (See Table 13-6 for a list of the MemberTypes.) MemberInfo has relatively few properties and methods. Here are some of the more useful. The MemberInfo.Name method returns the name of the type member. The MemberInfo.MetadataToken property returns the metadata token of the member. MemberInfo.ReflectedType provides the Type object of which the MemberInfo object is an instance.

Table 13-6. MemberTypes enumeration

MemberType

Value

MemberTypes.Constructor

0x01

MemberTypes.Custom

0x40

MemberTypes.Event

0x02

MemberTypes.Field

0x04

MemberTypes.Method

0x08

MemberTypes.NestedType

0x80

MemberTypes.Property

0x10

MemberTypes.TypeInfo

0x20

MemberTypes.All

0xBF

As mentioned, Type.GetMembers returns a collection that contains all the members of the reflected type. You can be somewhat more specific. 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 13-7 lists the methods that return specific collections (the nonplural version of the method returns a single item).

Table 13-7. Type methods that return metadata collections

Method

Returns

Type of returned item(s)

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 method returns a collection of MethodInfo elements and is overloaded to be called with no parameters or with a single parameter, which is BindingFlags:

MethodInfo[] GetMethods();
MethodInfo[] GetMethods(BindingFlags binding)

BindingFlags is a bitwise enumeration that expands or filters the results of a collection. For example, to include private members in a collection, specify the BindingFlags.NonPublic flag. Some BindingFlags, such as InvokeMember, are not applicable in all contexts. When stipulating BindingFlags, there are no default flags. You must specify the flag for every item desired in the collection.

The zero-argument version of GetMethods obviously does not have a parameter for BindingFlags. Instead, this version of the method has default bindings, which includes public methods. Notably, the BindingFlags.Static flag is not included and static methods are excluded from the method collection by default. The following code iterates private instance (nonpublic) members of a class first. Second, the 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("
{0} type of {1} assembly",
                targetType.Name, targetType.Assembly.GetName().Name);
            Console.WriteLine("
{0,22}
", "[ 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 calls Type.ReflectionOnlyGetType to load the type for inspection only. The DumpMethods function iterates the methods of this type, and then iterates the parameters of each method. The name of each method and parameter is displayed. The following command 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 with delegates are discussed in Chapter 10.

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 marginally slower. With delegates, the method signature of the function call must match the delegate. This is determined at compile time. 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 but less safe.

In reflection, there are two approaches to invoking a method dynamically: MethodInfo.Invoke and Type.InvokeMember. Using MethodInfo.Invoke is the simpler approach. However, Type.InvokeMember is more malleable. The basic syntax of MethodInfo.Invoke 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 should be null. To avoid an exception at run time, which is never desirable, care must be taken to ensure that the instance and parameters given to MethodInfo.Invoke match the signature of the function.

Here are the MethodInfo.Invoke overloaded signatures:

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

The second Invoke method has several additional parameters. The obj parameter is the instance to which the method is bound. The method is invoked on this object. If invoking a static method, the obj parameter should be null. BindingFlags is the next parameter and further describes the Invoke operation, such as Binding.InvokeMethod. This binding flag indicates that a method must be invoked. The default is BindingFlags.Default. BindingFlags.Default is not binding. Binderobj is used to select the appropriate candidate among overloaded methods. (Binders are discussed in the next section.) 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. Invoke returns the return value of the invoked method.

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

Here are the Type.InvokeMember overloaded signatures:

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 one of the overloaded methods, and it has five 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. (Binders are discussed in the next section.) The method binds to the typeInstance object. Next, arguments is the array of method parameters. InvokeMember2 adds another parameter, which is the culture parameter for setting the culture. InvokeMember3 is the final overload. The ParameterModifier parameter is an array of attributes for the method arguments. The namedParameters parameter is used to specify named parameters.

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

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 identify the specific method 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 explicitly 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 13-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 13-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

If a binder is provided, Binder.BindToMethod is called when a method is invoked dynamically. To override the default selection criteria for an overloaded method, first create a custom Binder class that inherits the Binder class. Override and implement or stub (partially implement) each abstract method of the base class. How the binder is used determines which methods of the base class (Binder) you should fully implement. To select a specific overloaded method, BindToMethod 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. Next, match is a MethodBase array with an element for each possible overloaded function candidate. If a method is overloaded three times, the match array should have three elements. The args array holds the values of the method parameters. The modifiers parameter is an array of ParameterModifier, which are the modifiers that apply to the parameters. The culture parameter sets the culture. The names parameter is an array of 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 not null, Binder.ReorderArgumentArray is called after BindToMethod and returns the parameters to the original order.

There are no rules 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 not written for general-purpose usage. It is a limited example for this book:

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("
 {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::two-argument Method " +
            argument.ToString() + ", " + argument2.ToString());
    }
}

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. BindToMethod is the only method fully implemented in the example. The implementation lists the signatures of each candidate. The appropriate method to invoke then is chosen in the method. More specifically, the first foreach loop iterates the candidates, 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 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 then is obtained from the ZClass instance, and Type.InvokeMember is called twice with the custom binder. InvokeMember is called to invoke "MethodA" dynamically, first with an integer parameter and then with a long parameter. Here 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 also can be created dynamically at run time. You can reflect, bind methods, and otherwise treat the dynamic object as a static object. As often is the case with Reflection, the primary benefit is added flexibility. What if the type or number of instances is not known 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 dynamically.

The Activator class can create instances at run time and is a member of the Reflection namespace. 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. Here is the list of the overloaded CreateInstance methods:

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

Some CreateInstance methods—and all CreateInstanceFrom methods—return an ObjectHandle 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 uncover a proxy to the remote object. Alternatively, the AppDomain.CreateInstanceFromAndUnwrap method creates an instance of the type and returns the proxy in one less step.

The following code creates three instances of a type that has been copied into different locations. 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 approaches to creating an instance of a type, and two approaches to binding a method to the type and invoking that method dynamically. Dynamically invoking a method through casting is a mechanism not previously demonstrated. The code for calling a method through casting is shown again here:

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

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.)

Either directly or indirectly, the 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 favorable performance benefits when compared with entirely late binding the type and method.

Late Binding Delegates

A delegate is a repository that contains 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. A compiler error occurs if there is a mismatch. Unlike MethodInfo, a function pointer is discriminatory and bound to a specific object or static class. MethodInfo is nondiscriminatory and can be associated with any object with a member that shares that method identity. 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 of this topic, read Chapter 10. 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 then is added to the delegate and invoked. First, the methods in the delegate are invoked using the Delegate.Invoke method. Second, the function is invoked through the delegate using the normal C# method-calling syntax—Invoke is called implicitly. At compile time, XDelegate expands into a class derived from a Delegate type. The signature of Invoke matches the signature of the delegate. Therefore, XDelegate.Invoke has two integer parameters, which enforces type safety on related function pointers.

The preceding code assumes that the delegate signature is known at compile time. If it is not, you could not define the delegate type. What if the signature of the delegate is not known at compile time? Ultimately, a delegate becomes a class. Like any other class, an instance of the delegate can be created at run time. You can then bind a method to the delegate and later invoke the method through the delegate. The Delegate.CreateDelegate and Delegate.DynamicInvoke methods allow this approach. As already mentioned, late binding of function pointers 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.

The Delegate.CreateDelegate method creates a new delegate at run time and then adds the function pointer to the delegate. CreateDelegate is an overloaded method where the essential parameters are the delegate type, target (for non-static methods), and method identity. The delegate type is the type of delegate being created. The target parameter is the object to bind the method to. If target is null or missing, the method must be static. The method parameter is the initial function pointer being assigned to the delegate. The signature of the specified method should match that of the delegate type. These are the overloaded CreateDelegate methods:

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 firstArgument, MethodInfo method,
    bool throwOnBindFailure)
static Delegate CreateDelegate(Type type, object firstArgument, Type target, string method)
static Delegate CreateDelegate(Type type, object firstArgument, Type target, string method,
    bool ignoreCase)
static Delegate CreateDelegate(Type type, object target, string method)
static Delegate CreateDelegate(Type type, object target, string method, bool ignoreCase)
static Delegate CreateDelegate(Type type, object target, string method, bool ignoreCase,
    bool throwOnBindFailure)
static Delegate CreateDelegate(Type type, Type target, string method)
static Delegate CreateDelegate(Type type, 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 function pointers added to the delegate. You cannot call Invoke on a delegate returned from CreateDelegate. This is a major difference between compile-time and run-time instances of delegates. An array of function arguments is the only parameter of DynamicInvoke.

Here is the DynamicInvoke signature:

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 approaches. For example, a simple call bound at compile time should be quicker than a method bound at run time. Depending on the application and usage patterns of the method, the difference might be material. Losing a few nanoseconds might be trivial for a user interface–driven application. However, a loss of a few nanoseconds in a server application multiplied by thousands of users, each with multiple transactions, can pose a real problem.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.135.225