Recipe 12.12 Dynamically Invoking Members
Problem
You have a list of
method names that you wish to invoke dynamically within your
application. As your code executes, it will pull names off of this
list and attempt to invoke these methods. This technique would be
useful to create a test harness for components that read in the
methods to execute from an XML file and execute them with the given
parameters.
Solution
The TestDynamicInvocation method opens the XML
configuration file, reads out the test information, and executes each
test method dynamically:
public static void TestDynamicInvocation( )
{
// read in the methods to run from the xml file
XmlDocument doc = new XmlDocument( );
doc.Load(@"C:\C#Cookbook\SampleClassLibraryTests.xml");
// get the tests to run
XmlNodeList nodes = doc.SelectNodes(@"Tests/Test");
// run each test method
foreach(XmlNode node in nodes)
{
object obj = DynamicInvoke(node,
@"C:\C#Cookbook\SampleClassLibrary.dll");
// print out the return
Console.WriteLine("\tReturned object: " + obj);
Console.WriteLine("\tReturned object: " + obj.GetType( ).FullName);
}
}
The XML document in which the test method information is contained
looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<Tests>
<Test className='SampleClassLibrary.SampleClass' methodName='TestMethod1'>
<Parameter>Running TestMethod1</Parameter>
</Test>
<Test className='SampleClassLibrary.SampleClass' methodName='TestMethod2'>
<Parameter>Running TestMethod2</Parameter>
<Parameter>27</Parameter>
</Test>
</Tests>
The
DynamicInvoke method dynamically invokes the
method that is passed to it using the information contained in the
XmlNode. The parameters types are determined by
examining the ParameterInfo items on the
MethodInfo, and then the values provided are
converted to the actual type from a string via the
Convert.ChangeType method. Finally, the return
value of the invoked method is returned by this method. Its source
code is:
public static object DynamicInvoke(XmlNode testNode, string asmPath)
{
// Load the assembly
Assembly asm = Assembly.LoadFrom(asmPath);
// get the name of the type from the className attribute on Test
string typeName = testNode.Attributes.GetNamedItem("className").Value;
// get the name of the method from the methodName attribute on Test
string methodName = testNode.Attributes.GetNamedItem("methodName").Value;
// create the actual type
Type dynClassType = asm.GetType(typeName, true, false);
// Create an instance of this type and verify that it exists
object dynObj = Activator.CreateInstance(dynClassType);
if (dynObj != null)
{
// Verify that the method exists and get its MethodInfo obj
MethodInfo invokedMethod = dynClassType.GetMethod(methodName);
if (invokedMethod != null)
{
// Create the parameter list for the dynamically invoked methods
object[] parameters = new object[testNode.ChildNodes.Count];
int index = 0;
// for each parameter, add it to the list
foreach(XmlNode node in testNode.ChildNodes)
{
// get the type of the parameter
Type paramType =
invokedMethod.GetParameters( )[index].ParameterType;
// change the value to that type and assign it
parameters[index] =
Convert.ChangeType(node.InnerText,paramType);
index++;
}
// Invoke the method with the parameters
object retObj = invokedMethod.Invoke(dynObj, parameters);
// return the returned object
return (retObj);
}
}
return (null);
}
These are the dynamically invoked methods located on the
SampleClass type in the
SampleClassLibrary assembly:
public bool TestMethod1(string text)
{
Console.WriteLine(text);
return (true);
}
public bool TestMethod2(string text, int n)
{
Console.WriteLine(text + " invoked with {0}",n);
return (true);
}
The output from these methods looks like this:
Running TestMethod1
Returned object: True
Returned object: System.Boolean
Running TestMethod2 invoked with 27
Returned object: True
Returned object: System.Boolean
Discussion
Reflection possesses the ability to dynamically invoke both static
and instance methods existing within a type in either the same
assembly or a different one. This can be a very powerful tool to
allow your code to determine at runtime which method to call. This
determination can be based on an assembly name, a type name, or a
method name, though the assembly name is not required if the method
exists in the same assembly as the invoking code, or if you already
have the Assembly object, or if you have a
Type object for the class the method is on.
This technique may
seem similar to delegates since both can dynamically determine at
runtime which method is to be called. Delegates, on the whole,
require you to know signatures of methods you might call at runtime,
whereas with reflection, you can invoke methods where you have no
idea of the signature, providing a much looser binding. More dynamic
invocation can be achieved with
Delegate.DynamicInvoke, but this is more of a
reflection-based method than the traditional delegate invocation.
The DynamicInvoke method shown in the Solution
section contains all the code required to dynamically invoke a
method. This code first loads the type using its assembly name
(passed in through the asmName parameter).
Next, it gets the Type object for the class
containing the method to invoke (the class name is gotten from the
Test element's
className attribute). The method name is then
retrieved (from the Test
element's methodName attribute).
Once we have all of the information from the Test
element, an instance of the Type object is
created, and we then invoke the specified method on this created
instance:
First, the static Activator.CreateInstance method
is called to actually create an instance of the
Type object contained in the local variable
dynClassType. The method returns an object
reference to the instance of type that was
created, or an exception is thrown if the object cannot be created. Once we have successfully obtained the instance of this class, the
MethodInfo object of the method to be invoked is
acquired through a call to GetMethod on the object
instance just returned by the CreateInstance
method.
The instance of the object created with the
CreateInstance method is then passed as the first
parameter to the MethodInfo.Invoke method. This
method returns an object containing the return value of the invoked
method, or null if the return value is
void. This object is then returned by the
DynamicInvoke method. The second parameter to
MethodInfo.Invoke is an object array containing
any parameters to be passed to this method. This array is constructed
based on the number of Parameter elements under
each Test element in the XML, we then look at the
ParameterInfo of each parameter (gotten from
MethodInfo.GetParameters( )) and use the
Convert.ChangeType method to coerce the string
value from the XML to the proper type.
The TestDynamicInvoke method finally displays each
returned object value and its type. Note that there is no extra logic
required to return different return values from the invoked methods
since they are all returned as an object, unlike passing differing
arguments to the invoked methods.
See Also
See the "Activator Class,"
"MethodInfo Class,"
"Convert.ChangeType Method," and
"ParameterInfo Class" topics in the
MSDN documentation.
|