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.

