AddThis Social Bookmark Button

Print

Programming C#: Attributes and Reflection
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9

Late Binding

Once you have discovered a method, it's possible to invoke it using reflection. For example, you might like to invoke the Cos( ) method of System.Math, which returns the cosine of an angle.

TIP: You could, of course, call Cos( ) in the normal course of your code, but reflection allows you to bind to that method at runtime. This is called late-binding and offers the flexibility of choosing at runtime which object you will bind to and invoking it programmatically. This can be useful when creating a custom script to be run by the user or when working with objects that might not be available at compile time. For example, by using late-binding, your program can interact with the spellchecker or other components of a running commercial word processing program such as Microsoft Word.

To invoke Cos( ), you will first get the Type information for the System.Math class:

Type theMathType = Type.GetType("System.Math");

With that type information, you can dynamically load an instance of that class by using a static method of the Activator class.

The Activator class contains four methods, all static, which you can use to create objects locally or remotely or to obtain references to existing objects. The four methods are: CreateComInstanceFrom, CreateInstanceFrom, GetObject, and CreateInstance:

CreateComInstanceFrom
Used to create instances of COM objects.
CreateInstanceFrom
Used to create a reference to an object from a particular assembly and type name.
GetObject
Used when marshaling objects. Marshaling is discussed in detail in Chapter 19.
CreateInstance
Used to create local or remote instances of an object. You'll use this method to instantiate an object of the System.Math class.
Object theObj = Activator.CreateInstance(theMathType);

You now have two objects in hand: a Type object named TheMathType, which you created by calling GetType, and an instance of the System.Math class named theObj, which you instantiated by calling CreateInstance.

Before you can invoke a method on the object, you must get the method you need from the Type object, theMathType. To do so, you'll call GetMethod(), and you'll pass in the signature of the Cos method.

The signature, you will remember, is the name of the method (Cos) and its parameter types. In the case of Cos(), there is only one parameter: a double. Whereas, Type.GetMethod takes two parameters: the first represents the name of the method you want, and the second represents the parameters. The name is passed as a string; the parameters are passed as an array of types:

MethodInfo CosineInfo = 
   theMathType.GetMethod("Cos",paramTypes);

Before calling GetMethod, you must prepare the array of types:

Type[] paramTypes = new Type[1];
paramTypes[0]= Type.GetType("System.Double");

This code declares the array of Type objects and then fills the first element (paramTypes[0]) with a Type representing a double. You obtain that type representing a double by calling the static method Type.GetType(), passing in the string System.Double.

You now have an object of type MethodInfo on which you can invoke the method. To do so, you must pass in the actual value of the parameters, again in an array:

Object[] parameters = new Object[1];
parameters[0] = 45;
Object returnVal = CosineInfo.Invoke(theObj,parameters);

TIP: Note that you've created two arrays. The first, paramTypes, holds the type of the parameters. The second, parameters, holds the actual value. If the method had taken two arguments, you'd have declared these arrays to hold two values. If the method took no values, you still would create the array, but you would give it a size of zero!

Type[] paramTypes = new Type[0];

Odd as this looks, it is correct.

Example 18-7 illustrates dynamically calling the Cos( ) method.


Example 18-7: Dynamically invoking a method

namespace Programming_CSharp
{
  using System;
  using System.Reflection;
 
  public class Tester
  {
   public static void Main( )
   {
     Type theMathType = Type.GetType("System.Math");
     Object theObj =
      Activator.CreateInstance(theMathType);
 
     // array with one member
     Type[] paramTypes = new Type[1];
     paramTypes[0]= Type.GetType("System.Double");
 
     // Get method info for Cos( )
     MethodInfo CosineInfo =
      theMathType.GetMethod("Cos",paramTypes);
 
     // fill an array with the actual parameters
     Object[] parameters = new Object[1];
     parameters[0] = 45;
     Object returnVal =
      CosineInfo.Invoke(theObj,parameters);
     Console.WriteLine(
      "The cosine of a 45 degree angle {0}",
      returnVal);
 
   }
  }
}

That was a lot of work just to invoke a single method. The power, however, is that you can use reflection to discover an assembly on the user's machine, use reflection to query what methods are available, and then use reflection to invoke one of those members dynamically!

Reflection Emit

So far we've seen reflection used for three purposes: viewing metadata, type discovery, and dynamic invocation. You might use these techniques when building tools (such as a development environment) or when processing scripts. The most powerful use of reflection, however, is with reflection emit.

Reflection emit supports the dynamic creation of new types at runtime. You can define an assembly to run dynamically or to save itself to disk, and you can define modules and new types with methods that you can then invoke.

TIP: The use of dynamic invocation and reflection emit should be considered an advanced topic. Most developers will never have need to use reflection emit. This demonstration is based on an example provided at the Microsoft Author's Summit, Fall 2000.

To understand the power of reflection emit, you must first consider a slightly more complicated example of dynamic invocation.

Problems can have general solutions that are relatively slow and specific solutions that are fast. To keep things manageably simple, consider a DoSum( ) method, which provides the sum of a string of integers from 1...n, where n will be supplied by the user.

Thus, DoSum(3) is equal to 1+2+3, or 6. DoSum(10) is 55. Writing this in C# is very simple:

public int DoSum1(int n)
{
    int result = 0;
    for(int i = 1;i <= n; i++)
    {
        result += i;
    }
    return result;

The method simply loops, adding the requisite number. If you pass in 3, the method adds 1 + 2 + 3 and returns an answer of 6.

With large numbers, and when run many times, this might be a bit slow. Given the value 20, this method would be considerably faster if you removed the loop:

public int DoSum2( )
{
  return 1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20;
}

DoSum2 runs more quickly than DoSum1 does. How much more quickly? To find out, you'll need to put a timer on both methods. To do so, you'll use a DateTime object to mark the start time and a TimeSpan object to compute the elapsed time.

For this experiment, you need to create two DoSum( ) methods; the first will use the loop and the second will not. You will call each 1,000,000 times. (Computers are very fast, so to see a difference you have to work hard!) You'll then compare the times. Example 18-8 illustrates the entire test program.


Example 18-8: Comparing loop to brute force

namespace Programming_CSharp
{
   using System;
   using System.Diagnostics;
   using System.Threading;
 
   public class MyMath
   {
      // sum numbers with a loop
      public int DoSum(int n)
      {
         int result = 0;
         for(int i = 1; i <= n; i++)
         {
            result += i;
         }
         return result;
      }
 
      // brute force by hand
      public int DoSum2(  )
      {
         return 1+2+3+4+5+6+7+8+9+10+11
            +12+13+14+15+16+17+18+19+20;
      }
 
   }
 
   public class TestDriver
   {
      public static void Main(  )
      {
 
         const int val = 20;  // val to sum
 
         // 1,000,000 iterations
         const int iterations = 1000000;
 
         // hold the answer
         int result = 0;
 
         MyMath m = new MyMath(  );            
 
         // mark the start time   
         DateTime startTime = DateTime.Now;
 
         // run the experiment
         for (int i = 0;i < iterations;i++)
         {
            result = m.DoSum(val);
         }
         // mark the elapsed time
         TimeSpan elapsed = 
            DateTime.Now - startTime;
 
         // display the results
         Console.WriteLine(
            "Loop: Sum of ({0}) = {1}",
               val, result);
         Console.WriteLine(
            "The elapsed time in milliseconds is: " + 
            elapsed.TotalMilliseconds.ToString(  ));
 
         // mark a new start time
         startTime = DateTime.Now;
 
         // run the experiment
         for (int i = 0;i < iterations;i++)
         {
            result = m.DoSum2(  );
         }
 
         // mark the new elapsed time
         elapsed = DateTime.Now - startTime;
 
         // display the results
         Console.WriteLine(
            "Brute Force: Sum of ({0}) = {1}",
               val, result);
         Console.WriteLine(
            "The elapsed time in milliseconds is: " + 
            elapsed.TotalMilliseconds);
      }
   }
}

Output:

Loop: Sum of (20) = 210
The elapsed time in milliseconds is: 187.5
Brute Force: Sum of (20) = 210
The elapsed time in milliseconds is: 31.25

As you can see, both methods returned the same answer (one million times!), but the brute-force method was six times faster.

Is there a way to avoid the loop and still provide a general solution? In traditional programming, the answer would be no, but with reflection you do have one other option. You can, at runtime, take the value the user wants (20, in this case) and write out to disk a class that implements the brute-force solution. You can then use dynamic invocation to invoke that method.

There are at least three ways to achieve this result, each increasingly elegant. The third, reflection emit, is the best, but a close look at two other techniques is instructive. If you are pressed for time, you might wish to jump ahead to the section entitled "Dynamic Invocation with Reflection Emit", later in this chapter.

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9

Next Pagearrow