When type parameters are unknown at compile time, but are acquired dynamically at run time, what is the best way to call generic methods?
Consider the following example code - inside the Example() method, what is the simplest way to call 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>() { //... } }
#1 building
through the use of dynamic Type rather than reflection API greatly simplifies the call of generic methods that use type parameters known only at run time.
To use this technique, you must know the Type (not just an instance of the Type class) from the actual object. Otherwise, you must create the Type of object or use the standard reflection API Solution . You can use the Activator.CreateInstance Method to create an object.
If you want to call a generic method, its type is inferred in the "normal" usage, and then only objects of unknown type need to be converted to dynamic. Here is 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)" } } }
This is the output of the 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 parameter (by using the GetType() method) and the type of the generic parameter (by using the typeof operator).
By converting object parameters to dynamic types, we delay providing type parameters until runtime. When a Process method is called with a dynamic parameter, the compiler does not care about the type of the parameter. Compiler generated code checks at run time for the actual type of parameters passed (by using reflection) and selects the best invocation method. There is only one generic method, so you can call it with the appropriate type parameter.
In this example, the output is the same as 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 }); }
Versions with dynamic types are definitely shorter and easier to write. You also don't have to worry about the performance of calling this function multiple times. With the help of cache Mechanism, the next call with the same type of parameter should be faster. Of course, you can write code to cache the invoked delegate, but you can get this behavior for free by using the dynamic type.
If the generic method to be called does not have a parameter of a parameterized type (so its type parameter cannot be inferred), you can wrap the call of the generic method in the helper method, as shown 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) + ">"); } }
Increase type security
The real benefit of using dynamic objects instead of reflection API s is that you lose only this specific type of compile time checking that you know at run time. The compiler statically parses other parameters and method names as usual. If you delete or add more parameters, change their types, or rename method names, a compile time error occurs. This does not happen if the method name is supplied as a string in Type.GetMethod and the parameter is supplied as an array of objects in MethodInfo.Invoke.
Here's a simple example of how to catch some errors at compile time (the commented code) and at run time. It also shows how DLR attempts to resolve the method to be called.
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 execute a method again by casting the parameter to a dynamic type. Only validation of the first parameter type is deferred to runtime. If the name of the method you are calling does not exist, or other parameters are invalid (wrong number of parameters or wrong type), a compiler error occurs.
When you pass a dynamic parameter to a method, the call is binding . Method overload resolution occurs at run time and attempts to select the best overload. Therefore, if you call the ProcessItem method with an object of type BarItem, the non generic method will actually be called because it is more suitable for this type. However, a runtime error occurs when passing an Alpha type parameter because there is no method to handle the object (the generic method has constraints where t: iItem and the Alpha class do not implement this interface). But that's the point. The compiler does not have valid information for this call. As a programmer, you know this, and you should make sure that the code runs correctly.
Return type trap
When you call a non empty method with a parameter of a dynamic type, its return type may also be It's dynamic. . Therefore, if you change the previous example to the following code:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
The result object will be of type dynamic. This is because the compiler does not always know which method to call. If you know the return type of a function call, you should take his Implicit conversion Is the type required so that the rest of the code is static:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
If the types do not match, a runtime error occurs.
In fact, if you try to get the result value in the previous example, you will encounter a runtime error in the second loop iteration. This is because you are trying to save the return value of the void function.
#2 building
You need to use reflection to get the method started, and then use the MakeGenericMethod Provide type parameters to "construct" it:
MethodInfo method = typeof(Sample).GetMethod("GenericMethod"); MethodInfo generic = method.MakeGenericMethod(myType); generic.Invoke(this, null);
For static methods, pass null as the first parameter to Invoke. This has nothing to do with generic methods, it's just a normal reflection.
As mentioned before, starting with C 4, using dynamic is a lot easier - of course, if you can use type inference. It doesn't help when type inference is not available, such as the exact example in the question.
#3 building
Add Adrian Gallero's answer :
Calling generic methods from type information involves three steps.
TLDR: using type objects to call known generic methods can be done by the following methods:
((Action)GenericMethod<object>) .Method .GetGenericMethodDefinition() .MakeGenericMethod(typeof(string)) .Invoke(this, null);
Where genericmethod < Object > is the name of the method to call and any type that satisfies the general constraint.
(Action) matches the signature of the method to be called, that is, (func < string, string, int > or Action < bool >)
Step 1 get MethodInfo of general method definition
Method 1: use GetMethod() or GetMethods() with the appropriate type or binding flags.
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
Method 2: create a delegate, get the MethodInfo object, and then call GetGenericMethodDefinition.
From within the class containing the method:
MethodInfo method = ((Action)GenericMethod<object>) .Method .GetGenericMethodDefinition(); MethodInfo method = ((Action)StaticMethod<object>) .Method .GetGenericMethodDefinition();
From outside the class that contains the method:
MethodInfo method = ((Action)(new Sample()) .GenericMethod<object>) .Method .GetGenericMethodDefinition(); MethodInfo method = ((Action)Sample.StaticMethod<object>) .Method .GetGenericMethodDefinition();
In C, the name of the method, "ToString" or "GenericMethod" actually refers to a group of methods that may contain one or more methods. You don't know which method to refer to until you provide the type of the method parameter.
((action) GenericMethod < Object >) refers to the delegate of a specific method. ((func < string, int >) GenericMethod < Object >) refers to another overload of GenericMethod
Method 3: create a lambda expression containing the method call expression, get the MethodInfo object, and then get GetGenericMethodDefinition
MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)( (Sample v) => v.GenericMethod<object>() )).Body).Method.GetGenericMethodDefinition();
This is decomposed into
Create a lambda expression where the body is a call to the desired method.
Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();
Extract the body and convert it to MethodCallExpression
MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;
Get general method definition from method
MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();
Step 2 calls MakeGenericMethod to create a generic method of the appropriate type.
MethodInfo generic = method.MakeGenericMethod(myType);
Step 3 is to call the method with the appropriate parameters.
generic.Invoke(this, null);
#4 building
This is my basis Grax's answer Two cents, but for a general method, two parameters are required.
Suppose your method is defined in the Helpers class as follows:
public class Helpers { public static U ConvertCsvDataToCollection<U, T>(string csvData) where U : ObservableCollection<T> { //transform code here } }
As far as I'm concerned, the U type is always an observable collection, storing objects of type T.
Because I have predefined types, I first create "virtual" objects, which represent observable collections (U) and objects stored in them (T), and will be used below to get their types when I call Make.
object myCollection = Activator.CreateInstance(collectionType); object myoObject = Activator.CreateInstance(objectType);
Then call GetMethod to find your Generic function:
MethodInfo method = typeof(Helpers). GetMethod("ConvertCsvDataToCollection");
So far, the above call is almost the same as the one described above, but the difference is small when you need to pass multiple parameters to it.
You need to pass the Type [] array to the MakeGenericMethod function, which contains the Type of the "virtual" object created above:
MethodInfo generic = method.MakeGenericMethod( new Type[] { myCollection.GetType(), myObject.GetType() });
When you are finished, you need to call the Invoke method as described above.
generic.Invoke(null, new object[] { csvData });
This is done. Very attractive!
To update:
As @ Bevan emphasizes, when calling the MakeGenericMethod function, because it accepts parameters, it does not need to create arrays or objects to get types, because I can pass types directly to this function. As far as I'm concerned, because I predefine the type in another class, I just change the 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 two Type properties, which I set at run time based on the enumeration values passed to the constructor, and will provide me with related types, and then use them in MakeGenericMethod.
Thank you again for highlighting @ Bevan.
#5 building
No one offers a "classic reflection" solution, so here's 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 DynamicDictionaryFactory class above has a method
CreateDynamicGenericInstance(Type keyType, Type valueType)
An IDictionary instance is created and returned. In fact, the key and value types of the instance are specified on the call keyType and valueType.
This is a complete example of how to call this method to instantiate and use 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"); } } }
After executing the above console application, we will get the correct expected results:
Dictionary<String, int> "One": 1 "Two": 2 "Three": 3