AddThis Social Bookmark Button

Print

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

Dynamic Invocation with Interfaces

It turns out that dynamic invocation is particularly slow. You want to maintain the general approach of writing the class at runtime and compiling it on the fly. But rather than using dynamic invocation, you'd just like to call the method. One way to speed things up is to use an interface to call the ComputeSums( ) method directly.

To accomplish this, you need to change ReflectionTest.DoSum( ) from:

public double DoSum(int theValue)
{
    if (theType == null)
    {
        GenerateCode(theValue);
    }
    object[] arguments = new object[0];
    object retVal =
        theType.InvokeMember("ComputeSum",
        BindingFlags.Default | BindingFlags.InvokeMethod,
        null,
        theFunction,
        arguments);
    return (double) retVal;
}

to the following:

public double DoSum(int theValue)
{
    if (theComputer == null)
    {
        GenerateCode(theValue);
    }
    return (theComputer.ComputeSum(  ));
}

In this example, theComputer is an interface to an object of type BruteForceSum. It must be an interface and not an object because when you compile this program, theComputer won't yet exist; you'll create it dynamically.

Remove the declarations for thetype and theFunction and replace them with:

IComputer theComputer = null;

This declares theComputer to be an IComputer interface. At the top of your program, declare the interface:

public interface IComputer
{
    double ComputeSum(  );
}

When you create the BruteForceSum class, you must make it implement Icomputer:

wrtr.WriteLine(
"class {0} : Programming_CSharp.IComputer ", 
className); 

Save your program in a project file named Reflection, and modify compileString in GenerateCode as follows:

string compileString = "/c csc /optimize+ ";
compileString += "/r:\"Reflection.exe\" ";
compileString += "/target:library ";
compileString += "{0}.cs > compile.out";

The compile string will need to reference the ReflectionTest program itself (Reference.exe) so that the dynamically called compiler will know where to find the declaration of IComputer.

After you build the assembly, you will no longer assign the instance to theClass and then get the type for theType, as these variables are gone. Instead, you will assign the instance to the interface IComputer:

theComputer = (IComputer) a.CreateInstance(className);

You use the interface to invoke the method directly in DoSum:

return (theComputer.ComputeSum(  ));

Example 18-10 is the complete source code.


Example 18-10: Dynamic invocation with interfaces

namespace Programming_CSharp

{

  using System;

  using System.Diagnostics;

  using System.IO;

  using System.Reflection;

 

  // used to benchmark the looping approach

  public class MyMath

  {

    // sum numbers with a loop

    public int DoSumLooping(int initialVal)

    {

      int result = 0;

      for(int i = 1;i <=initialVal;i++)

      {

        result += i;

      }

      return result;

    }

  }

 
  public interface IComputer
  {
    double ComputeSum( );
  }

 

  // responsible for creating the BruteForceSums

  // class and compiling it and invoking the

  // DoSums method dynamically

  public class ReflectionTest

  {

    // the public method called by the driver

    public double DoSum(int theValue)

    {
      if (theComputer == null)
      {
        GenerateCode(theValue);
      }
      return (theComputer.ComputeSum( ));

    }

 

    // generate the code and compile it

    private void GenerateCode(int theVal)

    {

      // open the file for writing

      string fileName = "BruteForceSums";

      Stream s =

        File.Open(fileName + ".cs", FileMode.Create);

        StreamWriter wrtr = new StreamWriter(s);

        wrtr.WriteLine(

        "// Dynamically created BruteForceSums class");

 

      // create the class
      string className = "BruteForceSums";
      wrtr.WriteLine(
        "class {0} : Programming_CSharp.IComputer ",
        className);

      wrtr.WriteLine("{");

 

      // create the method

      wrtr.WriteLine("\tpublic double ComputeSum( )");

      wrtr.WriteLine("\t{");

      wrtr.WriteLine("\t// Brute force sum method");

      wrtr.WriteLine("\t// For value = {0}", theVal);

 

      // write the brute force additions

      wrtr.Write("\treturn 0");

      for (int i = 1;i<=theVal;i++)

      {

        wrtr.Write("+ {0}",i);

      }

      wrtr.WriteLine(";");   // finish method

      wrtr.WriteLine("\t}");   // end method

      wrtr.WriteLine("}");   // end class

 

      // close the writer and the stream

      wrtr.Close( );

      s.Close( );

 

      // Build the file

      ProcessStartInfo psi =

        new ProcessStartInfo( );

      psi.FileName = "cmd.exe";

 
      string compileString = "/c csc /optimize+ ";
      compileString += "/r:\"Reflection.exe\" ";
      compileString += "/target:library ";
      compileString += "{0}.cs > compile.out";
 

      psi.Arguments =

        String.Format(compileString, fileName);

      psi.WindowStyle = ProcessWindowStyle.Minimized;

 

      Process proc = Process.Start(psi);

      proc.WaitForExit( );   // wait at most 2 seconds

 

      // Open the file, and get a

      // pointer to the method info
      Assembly a =
        Assembly.LoadFrom(fileName + ".dll");
      theComputer = (IComputer) a.CreateInstance(className);
      File.Delete(fileName + ".cs"); // clean up
    }
    IComputer theComputer = null;

  }

 

  public class TestDriver

  {

    public static void Main( )

    {

      const int val = 200; // 1..200

      const int iterations = 100000;

      double result = 0;

 

      // run the benchmark

      MyMath m = new MyMath( );

      DateTime startTime = DateTime.Now;        

      for (int i = 0;i < iterations;i++)

      {

        result = m.DoSumLooping(val);

      }

      TimeSpan elapsed =

        DateTime.Now - startTime;

      Console.WriteLine(

        "Sum of ({0}) = {1}",val, result);

      Console.WriteLine(

        "Looping. Elapsed milliseconds: " +

        elapsed.TotalMilliseconds +

        " for {0} iterations", iterations);

 

      // run our reflection alternative

      ReflectionTest t = new ReflectionTest( );

 

      startTime = DateTime.Now;

      for (int i = 0;i < iterations;i++)

      {

        result = t.DoSum(val);

      }

 

      elapsed = DateTime.Now - startTime;

      Console.WriteLine(

        "Sum of ({0}) = {1}",val, result);

      Console.WriteLine(

        "Brute Force. Elapsed milliseconds: " +

        elapsed.TotalMilliseconds +

        " for {0} iterations", iterations);

    }

  }

}

Output:

Sum of (200) = 20100
Looping. Elapsed milliseconds: 
140.625 for 100000 iterations
Sum of (200) = 20100
Brute Force. Elapsed milliseconds: 
875 for 100000 iterations

This output is much more satisfying; our dynamically created brute-force method now runs nearly twice as fast as the loop does. But you can do a lot better than that with reflection emit.

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

Next Pagearrow