Calling a generic method with a type parameter known only at runtime can be greatly simplified by using a dynamic
type instead of the reflection API.
To use this technique the type must be known from the actual object (not just an instance of the Type
class). Otherwise, you have to create an object of that type or use the standard reflection API solution. You can create an object by using the Activator.CreateInstance method.
If you want to call a generic method, that in "normal" usage would have had its type inferred, then it simply comes to casting the object of unknown type to dynamic
. Here's an example:
class Alpha { }
class Beta { }
class Service
{
public void Process<T>(T item)
{
Console.WriteLine("item.GetType(): " + item.GetType()
+ "\ttypeof(T): " + typeof(T));
}
}
class Program
{
static void Main(string[] args)
{
var a = new Alpha();
var b = new Beta();
var service = new Service();
service.Process(a); // Same as "service.Process<Alpha>(a)"
service.Process(b); // Same as "service.Process<Beta>(b)"
var objects = new object[] { a, b };
foreach (var o in objects)
{
service.Process(o); // Same as "service.Process<object>(o)"
}
foreach (var o in objects)
{
dynamic dynObj = o;
service.Process(dynObj); // Or write "service.Process((dynamic)o)"
}
}
}
And here's the output of this program:
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
item.GetType(): Alpha typeof(T): System.Object
item.GetType(): Beta typeof(T): System.Object
item.GetType(): Alpha typeof(T): Alpha
item.GetType(): Beta typeof(T): Beta
Process
is a generic instance method that writes the real type of the passed argument (by using the GetType()
method) and the type of the generic parameter (by using typeof
operator).
By casting the object argument to dynamic
type we deferred providing the type parameter until runtime. When the Process
method is called with the dynamic
argument then the compiler doesn't care about the type of this argument. The compiler generates code that at runtime checks the real types of passed arguments (by using reflection) and choose the best method to call. Here there is only this one generic method, so it's invoked with a proper type parameter.
In this example, the output is the same as if you wrote:
foreach (var o in objects)
{
MethodInfo method = typeof(Service).GetMethod("Process");
MethodInfo generic = method.MakeGenericMethod(o.GetType());
generic.Invoke(service, new object[] { o });
}
The version with a dynamic type is definitely shorter and easier to write. You also shouldn't worry about performance of calling this function multiple times. The next call with arguments of the same type should be faster thanks to the caching mechanism in DLR. Of course, you can write code that cache invoked delegates, but by using the dynamic
type you get this behaviour for free.
If the generic method you want to call don't have an argument of a parametrized type (so its type parameter can't be inferred) then you can wrap the invocation of the generic method in a helper method like in the following example:
class Program
{
static void Main(string[] args)
{
object obj = new Alpha();
Helper((dynamic)obj);
}
public static void Helper<T>(T obj)
{
GenericMethod<T>();
}
public static void GenericMethod<T>()
{
Console.WriteLine("GenericMethod<" + typeof(T) + ">");
}
}
What is really great about using dynamic
object as a replacement for using reflection API is that you only lose compile time checking of this particular type that you don't know until runtime. Other arguments and the name of the method are staticly analysed by the compiler as usual. If you remove or add more arguments, change their types or rename method name then you'll get a compile-time error. This won't happen if you provide the method name as a string in Type.GetMethod
and arguments as the objects array in MethodInfo.Invoke
.
Below is a simple example that illustrates how some errors can be caught at compile time (commented code) and other at runtime. It also shows how the DLR tries to resolve which method to call.
interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }
class Program
{
static void Main(string[] args)
{
var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
for (int i = 0; i < objects.Length; i++)
{
ProcessItem((dynamic)objects[i], "test" + i, i);
//ProcesItm((dynamic)objects[i], "test" + i, i);
//compiler error: The name 'ProcesItm' does not
//exist in the current context
//ProcessItem((dynamic)objects[i], "test" + i);
//error: No overload for method 'ProcessItem' takes 2 arguments
}
}
static string ProcessItem<T>(T item, string text, int number)
where T : IItem
{
Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
typeof(T), text, number);
return "OK";
}
static void ProcessItem(BarItem item, string text, int number)
{
Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
}
}
Here we again execute some method by casting the argument to the dynamic
type. Only verification of first argument's type is postponed to runtime. You will get a compiler error if the name of the method you're calling doesn't exist or if other arguments are invalid (wrong number of arguments or wrong types).
When you pass the dynamic
argument to a method then this call is lately bound. Method overload resolution happens at runtime and tries to choose the best overload. So if you invoke the ProcessItem
method with an object of BarItem
type then you'll actually call the non-generic method, because it is a better match for this type. However, you'll get a runtime error when you pass an argument of the Alpha
type because there's no method that can handle this object (a generic method has the constraint where T : IItem
and Alpha
class doesn't implement this interface). But that's the whole point. The compiler doesn't have information that this call is valid. You as a programmer know this, and you should make sure that this code runs without errors.
When you're calling a non-void method with a parameter of dynamic type, its return type will probably be dynamic
too. So if you'd change previous example to this code:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
then the type of the result object would be dynamic
. This is because the compiler don't always know which method will be called. If you know the return type of the function call then you should implicitly convert it to the required type so the rest of the code is statically typed:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
You'll get a runtime error if the type doesn't match.
Actually, if you try to get the result value in the previous example then you'll get a runtime error in the second loop iteration. This is because you tried to save the return value of a void function.