What's the best way to call a generic method when the type parameter isn't known at compile time, but instead is obtained dynamically at runtime?
Consider the following sample code - inside the Example()
method, what's the most concise way to invoke GenericMethod<T>()
using the Type
stored in the myType
variable?
public class Sample
{
public void Example(string typeName)
{
Type myType = FindType(typeName);
// What goes here to call GenericMethod<T>()?
GenericMethod<myType>(); // This doesn't work
// What changes to call StaticMethod<T>()?
Sample.StaticMethod<myType>(); // This also doesn't work
}
public void GenericMethod<T>()
{
// ...
}
public static void StaticMethod<T>()
{
//...
}
}
10 Answers 10
You need to use reflection to get the method to start with, then "construct" it by supplying type arguments with MakeGenericMethod:
MethodInfo method = typeof(Sample).GetMethod(nameof(Sample.GenericMethod));
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
For a static method, pass null
as the first argument to Invoke
. That's nothing to do with generic methods - it's just normal reflection.
As noted, a lot of this is simpler as of C# 4 using dynamic
- if you can use type inference, of course. It doesn't help in cases where type inference isn't available, such as the exact example in the question.
-
119+1; note that
GetMethod()
only considers public instance methods by default, so you may needBindingFlags.Static
and/orBindingFlags.NonPublic
.user565869– user5658692011年12月16日 22:32:44 +00:00Commented Dec 16, 2011 at 22:32 -
31The correct combination of flags is
BindingFlags.NonPublic | BindingFlags.Instance
(and optionallyBindingFlags.Static
).Lars Kemmann– Lars Kemmann2013年02月15日 19:10:26 +00:00Commented Feb 15, 2013 at 19:10 -
5A question getting marked dupe of this wonders how to do this with static methods - and technically so does the question here. generic.Invoke()'s first parameter should be null when calling static methods. The first parameter is only necessary when calling instance methods.Chris Moschini– Chris Moschini2013年03月22日 21:30:29 +00:00Commented Mar 22, 2013 at 21:30
-
2@ChrisMoschini: Added that to the answer.Jon Skeet– Jon Skeet2013年03月22日 21:32:05 +00:00Commented Mar 22, 2013 at 21:32
-
3@gzou: I've added something to the answer - but note that for calling the generic methods in the question,
dynamic
doesn't help because type inference isn't available. (There are no arguments the compiler can use to determine the type argument.)Jon Skeet– Jon Skeet2015年04月24日 09:44:31 +00:00Commented Apr 24, 2015 at 9:44
Just an addition to the original answer. While this will work:
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
It is also a little dangerous in that you lose compile-time check for GenericMethod
. If you later do a refactoring and rename GenericMethod
, this code won't notice and will fail at run time. Also, if there is any post-processing of the assembly (for example obfuscating or removing unused methods/classes) this code might break too.
So, if you know the method you are linking to at compile time, and this isn't called millions of times so overhead doesn't matter, I would change this code to be:
Action<> GenMethod = GenericMethod<int>; //change int by any base type
//accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
While not very pretty, you have a compile time reference to GenericMethod
here, and if you refactor, delete or do anything with GenericMethod
, this code will keep working, or at least break at compile time (if for example you remove GenericMethod
).
Other way to do the same would be to create a new wrapper class, and create it through Activator
. I don't know if there is a better way.
-
6In cases where reflection is used to call a method, it's usual that the method name is itself discovered by another method. Knowing the method name in advance isn't common.Bevan– Bevan2011年02月27日 21:59:22 +00:00Commented Feb 27, 2011 at 21:59
-
16Well, I agree for common uses of reflection. But the original question was how to call "GenericMethod<myType>()" If that syntax was allowed, we wouldn't need GetMethod() at all. But for the question "how do I write "GenericMethod<myType>"? I think the answer should include a way to avoid losing the compile-time link with GenericMethod. Now if this question is common or not I don't know, but I do know I had this exact problem yesterday, and that's why I landed in this question.Adrian Gallero– Adrian Gallero2011年02月28日 21:24:57 +00:00Commented Feb 28, 2011 at 21:24
-
21You could do
GenMethod.Method.GetGenericMethodDefinition()
instead ofthis.GetType().GetMethod(GenMethod.Method.Name)
. It’s slightly cleaner and probably safer.Daniel Cassidy– Daniel Cassidy2011年05月10日 10:10:34 +00:00Commented May 10, 2011 at 10:10 -
45Now you can use
nameof(GenericMethod)
dmigo– dmigo2016年03月17日 10:02:18 +00:00Commented Mar 17, 2016 at 10:02 -
2@EricScherrer: It should be
Action
notAction<>
Ben Voigt– Ben Voigt2022年07月14日 17:59:08 +00:00Commented Jul 14, 2022 at 17:59
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) + ">");
}
}
Increased type safety
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.
Return type gotcha
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.
-
Mariusz, confused by "However, you'll get runtime error when you pass argument of Alpha type because there's no method that can handle this object. " If I call var a = new Alpha() ProcessItem(a,"test" + i, i) Why wouldn't the generic ProcessItem method handle this effectively, outputting "General Process Item"?Alex Edelstein– Alex Edelstein2015年03月21日 18:51:42 +00:00Commented Mar 21, 2015 at 18:51
-
1@AlexEdelstein I edited my answer to clarify a bit. It's because generic
ProcessItem
method has generic constraint and accepts only object that implementsIItem
interface. When you will callProcessItem(new Aplha(), "test" , 1);
orProcessItem((object)(new Aplha()), "test" , 1);
you'll get a compiler error but when casting todynamic
you postpone that check to runtime.Mariusz Pawelski– Mariusz Pawelski2015年03月23日 11:38:33 +00:00Commented Mar 23, 2015 at 11:38 -
Great answer and explanation, works perfectly for me. Much better than the accepted answer, shorter to write, more performant, and safer.ygoe– ygoe2015年08月28日 09:08:22 +00:00Commented Aug 28, 2015 at 9:08
Adding on to Adrian Gallero's answer:
Calling a generic method from type info involves three steps.
##TLDR: Calling a known generic method with a type object can be accomplished by:##
((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition()
.MakeGenericMethod(typeof(string))
.Invoke(this, null);
where GenericMethod<object>
is the method name to call and any type that satisfies the generic constraints.
(Action) matches the signature of the method to be called i.e. (Func<string,string,int>
or Action<bool>
)
##Step 1 is getting the MethodInfo for the generic method definition##
###Method 1: Use GetMethod() or GetMethods() with appropriate types or binding flags.###
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
###Method 2: Create a delegate, get the MethodInfo object and then call GetGenericMethodDefinition
From inside the class that contains the methods:
MethodInfo method = ((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition();
MethodInfo method = ((Action)StaticMethod<object>)
.Method
.GetGenericMethodDefinition();
From outside of the class that contains the methods:
MethodInfo method = ((Action)(new Sample())
.GenericMethod<object>)
.Method
.GetGenericMethodDefinition();
MethodInfo method = ((Action)Sample.StaticMethod<object>)
.Method
.GetGenericMethodDefinition();
In C#, the name of a method, i.e. "ToString" or "GenericMethod" actually refers to a group of methods that may contain one or more methods. Until you provide the types of the method parameters, it is not known which method you are referring to.
((Action)GenericMethod<object>)
refers to the delegate for a specific method. ((Func<string, int>)GenericMethod<object>)
refers to a different overload of GenericMethod
###Method 3: Create a lambda expression containing a method call expression, get the MethodInfo object and then GetGenericMethodDefinition
MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
(Sample v) => v.GenericMethod<object>()
)).Body).Method.GetGenericMethodDefinition();
This breaks down to
Create a lambda expression where the body is a call to your desired method.
Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();
Extract the body and cast to MethodCallExpression
MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;
Get the generic method definition from the method
MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();
##Step 2 is calling MakeGenericMethod to create a generic method with the appropriate type(s).##
MethodInfo generic = method.MakeGenericMethod(myType);
##Step 3 is invoking the method with the appropriate arguments.##
generic.Invoke(this, null);
With C# 4.0, reflection isn't necessary as the DLR can call it using runtime types. Since using the DLR library is kind of a pain dynamically (instead of the C# compiler generating code for you), the open source framework Dynamitey (.net standard 1.5) gives you easy cached run-time access to the same calls the compiler would generate for you.
var name = InvokeMemberName.Create;
Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));
var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));
Nobody provided the "classic Reflection" solution, so here is a complete code example:
using System;
using System.Collections;
using System.Collections.Generic;
namespace DictionaryRuntime
{
public class DynamicDictionaryFactory
{
/// <summary>
/// Factory to create dynamically a generic Dictionary.
/// </summary>
public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
{
//Creating the Dictionary.
Type typeDict = typeof(Dictionary<,>);
//Creating KeyValue Type for Dictionary.
Type[] typeArgs = { keyType, valueType };
//Passing the Type and create Dictionary Type.
Type genericType = typeDict.MakeGenericType(typeArgs);
//Creating Instance for Dictionary<K,T>.
IDictionary d = Activator.CreateInstance(genericType) as IDictionary;
return d;
}
}
}
The above DynamicDictionaryFactory
class has a method
CreateDynamicGenericInstance(Type keyType, Type valueType)
and it creates and returns an IDictionary instance, the types of whose keys and values are exactly the specified on the call keyType
and valueType
.
Here is a complete example how to call this method to instantiate and use a Dictionary<String, int>
:
using System;
using System.Collections.Generic;
namespace DynamicDictionary
{
class Test
{
static void Main(string[] args)
{
var factory = new DictionaryRuntime.DynamicDictionaryFactory();
var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));
var typedDict = dict as Dictionary<String, int>;
if (typedDict != null)
{
Console.WriteLine("Dictionary<String, int>");
typedDict.Add("One", 1);
typedDict.Add("Two", 2);
typedDict.Add("Three", 3);
foreach(var kvp in typedDict)
{
Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
}
}
else
Console.WriteLine("null");
}
}
}
When the above console application is executed, we get the correct, expected result:
Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3
This is my 2 cents based on Grax's answer, but with two parameters required for a generic method.
Assume your method is defined as follows in an Helpers class:
public class Helpers
{
public static U ConvertCsvDataToCollection<U, T>(string csvData)
where U : ObservableCollection<T>
{
//transform code here
}
}
In my case, U type is always an observable collection storing object of type T.
As I have my types predefined, I first create the "dummy" objects that represent the observable collection (U) and the object stored in it (T) and that will be used below to get their type when calling the Make
object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);
Then call the GetMethod to find your Generic function:
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
So far, the above call is pretty much identical as to what was explained above but with a small difference when you need have to pass multiple parameters to it.
You need to pass an Type[] array to the MakeGenericMethod function that contains the "dummy" objects' types that were create above:
MethodInfo generic = method.MakeGenericMethod(
new Type[] {
myCollection.GetType(),
myObject.GetType()
});
Once that's done, you need to call the Invoke method as mentioned above.
generic.Invoke(null, new object[] { csvData });
And you're done. Works a charm!
UPDATE:
As @Bevan highlighted, I do not need to create an array when calling the MakeGenericMethod function as it takes in params and I do not need to create an object in order to get the types as I can just pass the types directly to this function. In my case, since I have the types predefined in another class, I simply changed my code to:
object myCollection = null;
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
MethodInfo generic = method.MakeGenericMethod(
myClassInfo.CollectionType,
myClassInfo.ObjectType
);
myCollection = generic.Invoke(null, new object[] { csvData });
myClassInfo contains 2 properties of type Type
which I set at run time based on an enum value passed to the constructor and will provide me with the relevant types which I then use in the MakeGenericMethod.
Thanks again for highlighting this @Bevan.
-
The arguments to
MakeGenericMethod()
have the params keyword so you don't need to create an array; nor do you need to create instances to get the types -methodInfo.MakeGenericMethod(typeof(TCollection), typeof(TObject))
would be sufficient.Bevan– Bevan2015年10月26日 21:58:12 +00:00Commented Oct 26, 2015 at 21:58
In spite this is a quite old question, I found it interesting, because there are several options to call a method dynamically. Literally, it is reflection, expression trees, and emitter. Historically, reflection is the slowest option, whilst emitter is the fastest one. Therefore, I've decided to compare them in this intriguing case and figure out if there are any changes nowadays. The original question asks for ** the best way to call a generic method when the type parameter isn't known at compile time**. However, almost all answers above suggested to use reflection.
I have created three test cases for all mentioned approaches. First, here is a slightly modified sample class that is going to be tested with 3 methods: TestReflection, TestExpression, and TestEmit.
public class Sample
{
public void TestDirectCall(Type type)
{
GenericMethod<string>();
GenericMethodWithArg<string>(42);
StaticMethod<string>();
StaticMethodWithArg<string>(6);
}
public void TestReflection(Type type)
{
CallViaReflection.CallGenericMethod(this, type);
CallViaReflection.CallGenericMethod(this, type, 42);
CallViaReflection.CallStaticMethod(type);
CallViaReflection.CallStaticMethod(type, 6);
}
public void TestExpression(Type type)
{
CallViaExpression.CallGenericMethod(this, type);
CallViaExpression.CallGenericMethod(this, type, 42);
CallViaExpression.CallStaticMethod(type);
CallViaExpression.CallStaticMethod(type, 6);
}
public void TestEmit(Type type)
{
CallViaEmit.CallGenericMethod(this, type);
CallViaEmit.CallGenericMethod(this, type, 42);
CallViaEmit.CallStaticMethod(type);
CallViaEmit.CallStaticMethod(type, 6);
}
public void T()
{
StaticMethod<string>();
}
public void GenericMethod<T>()
{
}
public void GenericMethodWithArg<T>(int someValue)
{
}
public static void StaticMethod<T>()
{
}
public static void StaticMethodWithArg<T>(int someValue)
{
}
}
The class CallViaReflection represents a helper class that makes calls of generic methods via reflection. I have decided to introduce a cache for better results.
public static class CallViaReflection
{
private readonly static Cache<MethodInfo> cache = new();
public static void CallGenericMethod(Sample sample, Type genericType)
{
var callDelegate = GetDelegate(nameof(Sample.GenericMethod), BindingFlags.Instance | BindingFlags.Public, genericType);
callDelegate.Invoke(sample, null);
}
public static void CallGenericMethod(Sample sample, Type genericType, int someValue)
{
var callDelegate = GetDelegate(nameof(Sample.GenericMethodWithArg), BindingFlags.Instance | BindingFlags.Public, genericType, typeof(int));
callDelegate.Invoke(sample, new object[] { someValue });
}
public static void CallStaticMethod(Type genericType)
{
var callDelegate = GetDelegate(nameof(Sample.StaticMethod), BindingFlags.Static | BindingFlags.Public, genericType);
callDelegate.Invoke(null, null);
}
public static void CallStaticMethod(Type genericType, int someValue)
{
var callDelegate = GetDelegate(nameof(Sample.StaticMethodWithArg), BindingFlags.Static | BindingFlags.Public, genericType, typeof(int));
callDelegate.Invoke(null, new object[] { someValue });
}
private static MethodInfo GetDelegate(string methodName, BindingFlags bindingFlags, Type genericType, params Type[] arguments)
{
if (cache.TryGet(methodName, genericType, out var concreteMethodInfo))
return concreteMethodInfo;
var sampleType = typeof(Sample);
MethodInfo genericMethodInfo = sampleType.GetMethod(methodName, bindingFlags)!;
concreteMethodInfo = genericMethodInfo.MakeGenericMethod(genericType);
cache.Add(methodName, genericType, concreteMethodInfo);
return concreteMethodInfo;
}
}
The next class CallViaExpression uses cached expression trees.
public static class CallViaExpression
{
private static readonly Cache<Delegate> cache = new();
public static void CallGenericMethod(Sample sample, Type genericType)
{
var callDelegate = GetDelegate(nameof(Sample.GenericMethod), BindingFlags.Instance | BindingFlags.Public, genericType);
((Action<Sample>)callDelegate).Invoke(sample);
}
public static void CallGenericMethod(Sample sample, Type genericType, int someValue)
{
var callDelegate = GetDelegate(nameof(Sample.GenericMethodWithArg), BindingFlags.Instance | BindingFlags.Public, genericType, typeof(int));
((Action<Sample, int>)callDelegate).Invoke(sample, someValue);
}
public static void CallStaticMethod(Type genericType)
{
var callDelegate = GetDelegate(nameof(Sample.StaticMethod), BindingFlags.Static | BindingFlags.Public, genericType);
((Action)callDelegate).Invoke();
}
public static void CallStaticMethod(Type genericType, int someValue)
{
var callDelegate = GetDelegate(nameof(Sample.StaticMethodWithArg), BindingFlags.Static | BindingFlags.Public, genericType, typeof(int));
((Action<int>)callDelegate).Invoke(someValue);
}
private static Delegate GetDelegate(string methodName, BindingFlags bindingFlags, Type genericType, params Type[] arguments)
{
if (cache.TryGet(methodName, genericType, out var callDelegate))
return callDelegate;
var sampleType = typeof(Sample);
MethodInfo genericMethodInfo = sampleType.GetMethod(methodName, bindingFlags)!;
var concreteMethodInfo = genericMethodInfo.MakeGenericMethod(genericType);
var argumentExpr = arguments.Select((type, i) => Expression.Parameter(type, "arg" + i)).ToArray();
if (concreteMethodInfo.IsStatic)
{
var callExpr = Expression.Call(concreteMethodInfo, argumentExpr);
callDelegate = Expression.Lambda(callExpr, argumentExpr).Compile();
}
else
{
var parameterExpr = Expression.Parameter(sampleType, "sample");
var callExpr = Expression.Call(parameterExpr, concreteMethodInfo, argumentExpr);
callDelegate = Expression.Lambda(callExpr, new[] { parameterExpr }.Union(argumentExpr).ToArray()).Compile();
}
cache.Add(methodName, genericType, callDelegate);
return callDelegate;
}
}
And the last but not least CallViaEmit emits necessary operations.
public static class CallViaEmit
{
private static readonly Cache<Delegate> cache = new();
public static void CallGenericMethod(this Sample sample, Type genericType)
{
var callDelegate = GetDynamicMethod(nameof(Sample.GenericMethod), BindingFlags.Instance | BindingFlags.Public, genericType);
((Action<Sample>)callDelegate).Invoke(sample);
}
public static void CallGenericMethod(this Sample sample, Type genericType, int someValue)
{
var callDelegate = GetDynamicMethod(nameof(Sample.GenericMethodWithArg), BindingFlags.Instance | BindingFlags.Public, genericType);
((Action<Sample, int>)callDelegate).Invoke(sample, someValue);
}
public static void CallStaticMethod(Type genericType)
{
var callDelegate = GetDynamicMethod(nameof(Sample.StaticMethod), BindingFlags.Static | BindingFlags.Public, genericType);
((Action)callDelegate).Invoke();
}
public static void CallStaticMethod(Type genericType, int someValue)
{
var callDelegate = GetDynamicMethod(nameof(Sample.StaticMethodWithArg), BindingFlags.Static | BindingFlags.Public, genericType);
((Action<int>)callDelegate).Invoke(someValue);
}
private static Delegate GetDynamicMethod(string methodName, BindingFlags bindingFlags, Type genericType)
{
if (cache.TryGet(methodName, genericType, out var callDelegate))
return callDelegate;
var genericMethodInfo = typeof(Sample).GetMethod(methodName, bindingFlags)!;
var concreteMethodInfo = genericMethodInfo.MakeGenericMethod(genericType);
var argumentTypes = concreteMethodInfo.GetParameters().Select(x => x.ParameterType).ToArray(); ;
var dynamicMethodArgs = concreteMethodInfo.IsStatic
? argumentTypes
: new[] { typeof(Sample) }.Union(argumentTypes).ToArray();
var dynamicMethod = new DynamicMethod("DynamicCall", null, dynamicMethodArgs);
var il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Nop);
switch (dynamicMethodArgs.Length)
{
case 0:
break;
case 1:
il.Emit(OpCodes.Ldarg_0);
break;
case 2:
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
break;
case 3:
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
break;
default:
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldarg_3);
for (int i = 4; i < argumentTypes.Length; i++)
{
il.Emit(OpCodes.Ldarg, argumentTypes[i]);
}
break;
}
il.EmitCall(concreteMethodInfo.IsStatic ? OpCodes.Call : OpCodes.Callvirt, concreteMethodInfo, null);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ret);
callDelegate = dynamicMethod.CreateDelegate(GetActionType(dynamicMethodArgs));
cache.Add(methodName, genericType, callDelegate);
return callDelegate;
}
private static Type GetActionType(Type[] argumentTypes)
{
switch (argumentTypes.Length)
{
case 0:
return typeof(Action);
case 1:
return typeof(Action<>).MakeGenericType(argumentTypes);
case 2:
return typeof(Action<,>).MakeGenericType(argumentTypes);
case 3:
return typeof(Action<,,>).MakeGenericType(argumentTypes);
case 4:
return typeof(Action<,,,>).MakeGenericType(argumentTypes);
case 5:
return typeof(Action<,,,,>).MakeGenericType(argumentTypes);
case 6:
return typeof(Action<,,,,,>).MakeGenericType(argumentTypes);
case 7:
return typeof(Action<,,,,,,>).MakeGenericType(argumentTypes);
case 8:
return typeof(Action<,,,,,,,>).MakeGenericType(argumentTypes);
default:
throw new NotSupportedException("Action with more than 8 arguments is not supported");
}
}
}
Finally, here is a test class and test results.
[TestFixture]
public class SampleTests
{
private const int Iterations = 10000000;
[Test]
public void TestDirectCall()
{
var sample = new Sample();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < Iterations; i++)
sample.TestDirectCall(typeof(string));
stopwatch.Stop();
Assert.Pass($"Calling methods directly took {stopwatch.ElapsedMilliseconds} milliseconds.");
}
[Test]
public void TestReflection()
{
var sample = new Sample();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < Iterations; i++)
sample.TestReflection(typeof(string));
stopwatch.Stop();
Assert.Pass($"Calling methods dynamically via reflection took {stopwatch.ElapsedMilliseconds} milliseconds.");
}
[Test]
public void TestExpressionTree()
{
var sample = new Sample();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < Iterations; i++)
sample.TestExpression(typeof(string));
stopwatch.Stop();
Assert.Pass($"Calling methods dynamically via expression tree took {stopwatch.ElapsedMilliseconds} milliseconds.");
}
[Test]
public void TestEmit()
{
var sample = new Sample();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < Iterations; i++)
sample.TestEmit(typeof(string));
stopwatch.Stop();
Assert.Pass($"Calling methods dynamically via emit took {stopwatch.ElapsedMilliseconds} milliseconds.");
}
}
Calling methods dynamically via emit took 2939 milliseconds. Calling methods dynamically via expression tree took 3910 milliseconds. Calling methods dynamically via reflection took 6381 milliseconds.
Obviously, the winner is emitter. It still performs more than twice faster. Second place is for expression trees.
Therefore, my verdict has not beeing changed for second decade yet. If you need to call a method dynamically, start going with expression trees. If your code is performance-critical, use ILGenerator and emit necessary calls. In spite, it might look complicated at first glance, all necessary MSIL instruction could be easily disassembled using ildasm.
The source code is available on GitHub.
-
1If your code is not performance-critical then I suggest you use the most readable option, that is reflection.Luca Cremonesi– Luca Cremonesi2023年09月15日 20:20:21 +00:00Commented Sep 15, 2023 at 20:20
-
@LucaCremonesi it's about habits. When I got used to use expression trees I stopped using reflection. Expression trees are quite easy when you understand them, but honestly learning curve is hardEvgenii Shmanev– Evgenii Shmanev2023年09月16日 21:20:00 +00:00Commented Sep 16, 2023 at 21:20
-
If you are working in a team, the code should be readable for average member of the team, including newcomers, not only your own habitMichael Freidgeim– Michael Freidgeim2024年07月13日 07:05:11 +00:00Commented Jul 13, 2024 at 7:05
-
@MichaelFreidgeim Does it mean that an average member should not learn how to make code better? :)Evgenii Shmanev– Evgenii Shmanev2024年07月16日 14:10:42 +00:00Commented Jul 16, 2024 at 14:10
-
Depends on the definition of "better code". If learning curve is hard, may be it is not a better codeMichael Freidgeim– Michael Freidgeim2024年07月18日 11:00:52 +00:00Commented Jul 18, 2024 at 11:00
Inspired by Enigmativity's answer - let's assume you have two (or more) classes, like
public class Bar { }
public class Square { }
and you want to call the method Foo<T>
with Bar
and Square
, which is declared as
public class myClass
{
public void Foo<T>(T item)
{
Console.WriteLine(typeof(T).Name);
}
}
Then you can implement an Extension method like:
public static class Extension
{
public static void InvokeFoo<T>(this T t)
{
var fooMethod = typeof(myClass).GetMethod("Foo");
var tType = typeof(T);
var fooTMethod = fooMethod.MakeGenericMethod(new[] { tType });
fooTMethod.Invoke(new myClass(), new object[] { t });
}
}
With this, you can simply invoke Foo
like:
var objSquare = new Square();
objSquare.InvokeFoo();
var objBar = new Bar();
objBar.InvokeFoo();
which works for every class. In this case, it will output:
Square
Bar
Using Jinget.Core NuGet package, suppose that you have the following generic method:
public class Test{
public string Get<T,R>(int a, bool b)=>return "something";
public static string StaticGet<T,R>(int a, bool b)=>return "something";
}
Then for the instance method, you can call this generic method like following:
typeof(Test) // => Class containing the generic method
.Call(
caller: new Test(), // => object used to call the instance method
name: "Get", // => generic method name
parameterTypes: [typeof(int),typeof[bool]], // => generic method input parameters types
parameterValues: [123, true], // => generic method input parameters values
generics: [typeof(TClassType),typeof[RClassType]); // => generic method types
And also for the static generic method, you can use the following code:
typeof(Test) // => Class containing the generic method
.Call(
name: "StaticGet",
parameterTypes: [typeof(int),typeof[bool]],
parameterValues: [123, true],
generics: [typeof(TClassType),typeof[RClassType]);
View many other test cases in Jinget GitHub page.
BindingFlags.Instance
, not justBindingFlags.NonPublic
, to get the private/internal method.