Choose the correct general method with reflection

I want to select the correct general method using reflection, and then call it.

This is usually pretty simple. for example

var method = typeof(MyType).GetMethod("TheMethod"); var typedMethod = method.MakeGenericMethod(theTypeToInstantiate); 

However, the problem arises when different general method overloads occur. For example, static methods in the System.Linq.Queryable class. There are two definitions of "Where'-method"

 static IQueryable<T> Where(this IQueryable<T> source, Expression<Func<T,bool>> predicate) static IQueryable<T> Where(this IQueryable<T> source, Expression<Func<T,int,bool>> predicate) 

This means that GetMethod is not working because it cannot parse two. Therefore, I want to choose the right one.

Until now, I often took the first or second method, depending on my need. Like this:

 var method = typeof (Queryable).GetMethods().First(m => m.Name == "Where"); var typedMethod = method.MakeGenericMethod(theTypeToInstantiate); 

However, I am not happy with this because I am making a huge assumption that the first method is correct. I rather want to find the correct method by argument type. But I could not understand how.

I tried it with passing "types", but that didn't work.

  var method = typeof (Queryable).GetMethod( "Where", BindingFlags.Static, null, new Type[] {typeof (IQueryable<T>), typeof (Expression<Func<T, bool>>)}, null); 

Does anyone have an idea how I can find the β€œright” general method through reflection. For example, the correct version of the Where method in the Queryable class?

+33
reflection c #
Sep 02 '10 at 21:45
source share
11 answers

It can be done, but it’s not very!

For example, to get the first Where overload indicated in your question, you can do this:

 var where1 = typeof(Queryable).GetMethods() .Where(x => x.Name == "Where") .Select(x => new { M = x, P = x.GetParameters() }) .Where(x => xPLength == 2 && xP[0].ParameterType.IsGenericType && xP[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>) && xP[1].ParameterType.IsGenericType && xP[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>)) .Select(x => new { xM, A = xP[1].ParameterType.GetGenericArguments() }) .Where(x => xA[0].IsGenericType && xA[0].GetGenericTypeDefinition() == typeof(Func<,>)) .Select(x => new { xM, A = xA[0].GetGenericArguments() }) .Where(x => xA[0].IsGenericParameter && xA[1] == typeof(bool)) .Select(x => xM) .SingleOrDefault(); 

Or if you want a second overload:

 var where2 = typeof(Queryable).GetMethods() .Where(x => x.Name == "Where") .Select(x => new { M = x, P = x.GetParameters() }) .Where(x => xPLength == 2 && xP[0].ParameterType.IsGenericType && xP[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>) && xP[1].ParameterType.IsGenericType && xP[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>)) .Select(x => new { xM, A = xP[1].ParameterType.GetGenericArguments() }) .Where(x => xA[0].IsGenericType && xA[0].GetGenericTypeDefinition() == typeof(Func<,,>)) .Select(x => new { xM, A = xA[0].GetGenericArguments() }) .Where(x => xA[0].IsGenericParameter && xA[1] == typeof(int) && xA[2] == typeof(bool)) .Select(x => xM) .SingleOrDefault(); 
+22
Sep 03 '10 at 0:10
source share

You can elegantly select a certain general method overload at compile time without passing a single line to temporary requests, like the other answers here.

Static methods

Suppose you have several static methods with the same name as:

 public static void DoSomething<TModel>(TModel model) public static void DoSomething<TViewModel, TModel>(TViewModel viewModel, TModel model) // etc 

If you create an Action or Func that matches the total score and the number of overload options you are looking for, you can select it at compile time with relatively little acrobatics.

Example: select the first method - it returns void, so use Action, it takes one common one. We use the object to not yet indicate the type:

 var method = new Action<object>(MyClass.DoSomething<object>); 

Example: select the second method - it returns void, therefore Action is of 2 generic types, therefore use the type object twice, once for each of two common parameters:

 var method = new Action<object, object>(MyClass.DoSomething<object, object>); 

You just got the method you need, without crazy plumbing, and without searching in time or using risky strings.

Methodinfo

Usually in Reflection, you need a MethodInfo object, which you can also get in a compiled way. This is when you pass the actual generic types that you want to use in your method. Assuming you need the second method above:

 var methodInfo = method.Method.MakeGenericMethod(type1, type2); 

Here is your general method without looking for reflections or calls to GetMethod () or fake strings.

Static Extension Methods

The specific example you are quoting from Queryable.Where the overload makes you look a bit fancy to define Func, but usually follows the same pattern. Signature for the most commonly used Where () extension method :

 public static IQueryable<TModel> Where<TModel>(this IQueryable<TModel>, Expression<Func<TModel, bool>>) 

Obviously, it will be a little more complicated - here it is:

 var method = new Func<IQueryable<object>, Expression<Func<object, bool>>, IQueryable<object>>(Queryable.Where<object>); var methodInfo = method.Method.MakeGenericMethod(modelType); 

Instance Methods

Enabling Valerie comment - in order to get the instance method, you need to do something very similar. Suppose you have this instance method in your class:

 public void MyMethod<T1>(T1 thing) 

First, select the method in the same way as for statics:

 var method = new Action<object>(MyMethod<object>); 

Then call GetGenericMethodDefinition() to go to the generic MethodInfo, and finally pass your type with MakeGenericMethod() :

 var methodInfo = method.Method.GetGenericMethodDefinition().MakeGenericMethod(type1); 

MethodInfo decoupling and parameter types

This was not asked in the question, but as soon as you do this, you can choose a method in one place and decide which types to pass it to another. You can separate these 2 steps.

If you are not sure about the generic type parameters that you are going to pass, you can always acquire a MethodInfo object without them.

Static

 var methodInfo = method.Method; 

Instance:

 var methodInfo = method.Method.GetGenericMethodDefinition(); 

And pass this to another method that knows the types it wants to create, and will call the method with - for example:

 processCollection(methodInfo, type2); ... protected void processCollection(MethodInfo method, Type type2) { var type1 = typeof(MyDataClass); object output = method.MakeGenericMethod(type1, type2).Invoke(null, new object[] { collection }); } 

One thing that helps a lot is to choose a specific class instance method from within the class and then expose it to external callers who need it with different types later.

Editing: Cleared explanations, included Valerie instance method example.

+40
Mar 22 '13 at 9:10
source share

This question has been around for 2 years, but I came up with an elegant solution, and thought that I would share it with great people here in StackOverflow. Hope this helps those who come here through various searches.

The problem, as stated by the poster, is to get the right general method. For example, the LINQ extension method can have a lot of overloads with type arguments nested in other common types, which are all used as parameters. I wanted to do something like this:

 var where = typeof(Enumerable).GetMethod( "Where", typeof(IQueryable<Refl.T1>), typeof(Expression<Func<Refl.T1, bool>> ); var group = typeof(Enumerable).GetMethod( "GroupBy", typeof(IQueryable<Refl.T1>), typeof(Expression<Func<Refl.T1, Refl.T2>> ); 

As you can see, I created several types of stubs "T1" and "T2", nested classes in the "Refl" class (a static class that contains all my various extension functions of the Reflection utility, etc. They serve as placeholders for which parameters There were usually types. The above examples correspond to the following LINQ methods:

 Enumerable.Where(IQueryable<TSource> source, Func<TSource, bool> predicate); Enumerable.GroupBy(IQueryable<Source> source, Func<TSource, TKey> selector); 

So, it should be clear that Refl.T1 goes where the TSource disappears in both of these calls; and Refl.T2 represents the TKey parameter. TX classes are declared as such:

 static class Refl { public sealed class T1 { } public sealed class T2 { } public sealed class T3 { } // ... more, if you so desire. } 

With three TX classes, your code can identify methods containing up to three type type parameters.

The next bit of magic is to implement a function that performs a search through GetMethods() :

 public static MethodInfo GetMethod(this Type t, string name, params Type[] parameters) { foreach (var method in t.GetMethods()) { // easiest case: the name doesn't match! if (method.Name != name) continue; // set a flag here, which will eventually be false if the method isn't a match. var correct = true; if (method.IsGenericMethodDefinition) { // map the "private" Type objects which are the type parameters to // my public "Tx" classes... var d = new Dictionary<Type, Type>(); var args = method.GetGenericArguments(); if (args.Length >= 1) d[typeof(T1)] = args[0]; if (args.Length >= 2) d[typeof(T2)] = args[1]; if (args.Length >= 3) d[typeof (T3)] = args[2]; if (args.Length > 3) throw new NotSupportedException("Too many type parameters."); var p = method.GetParameters(); for (var i = 0; i < p.Length; i++) { // Find the Refl.TX classes and replace them with the // actual type parameters. var pt = Substitute(parameters[i], d); // Then it a simple equality check on two Type instances. if (pt != p[i].ParameterType) { correct = false; break; } } if (correct) return method; } else { var p = method.GetParameters(); for (var i = 0; i < p.Length; i++) { var pt = parameters[i]; if (pt != p[i].ParameterType) { correct = false; break; } } if (correct) return method; } } return null; } 

The above code does most of the work β€” it iterates through all the methods of a particular type and compares them with the given types of parameters to search. But wait! How about this "replacement" function? This is a good little recursive function that will search the entire tree of parameter types. In the end, the type of the parameter itself can be a general type, which can contain Refl.TX types that must be replaced for parameters of the "real" type that are hidden from us.

 private static Type Substitute(Type t, IDictionary<Type, Type> env ) { // We only really do something if the type // passed in is a (constructed) generic type. if (t.IsGenericType) { var targs = t.GetGenericArguments(); for(int i = 0; i < targs.Length; i++) targs[i] = Substitute(targs[i], env); // recursive call t = t.GetGenericTypeDefinition(); t = t.MakeGenericType(targs); } // see if the type is in the environment and sub if it is. return env.ContainsKey(t) ? env[t] : t; } 
+18
Feb 29 2018-12-12T00:
source share

Another solution that may seem useful to you is to get MethodInfo based on Expression.Call , which already has the logic to allow overloading.

For example, if you need to get a specific Enumerable.Where method that can be executed using the following code:

 var mi = Expression.Call(typeof (Enumerable), "Where", new Type[] {typeof (int)}, Expression.Default(typeof (IEnumerable<int>)), Expression.Default(typeof (Func<int, int, bool>))).Method; 

The third argument in the example describes the types of common arguments and all other arguments are parameter types.

In the same way, even non-static general methods of an object can be obtained. You only need to change the first argument from typeof (YourClass) to Expression.Default(typeof (YourClass)) .

In fact, I used this approach in a plugin for the .NET Reflection API. You can check how it works here.

+4
Sep 24 '14 at 10:38
source share

Let the compiler do it for you:

 var fakeExp = (Expression<Func<IQueryable<int>, IQueryable<int>>>)(q => q.Where((x, idx) => x> 2)); var mi = ((MethodCallExpression)fakeExp.Body).Method.GetGenericMethodDefinition(); 

for a Where index with an index or just leave the second parameter in the Where expression for an object without

+3
Jun 04 '14 at 20:08
source share

Using DynamicMethods.GenericMethodInvokerMethod , GetMethod is not enough for use with generics

+2
Sep 02 2018-10-22T00:
source share

In addition to @MBoros answer.

You can avoid writing complex general arguments with this helper method:

 public static MethodInfo GetMethodByExpression<Tin, Tout>(Expression<Func<IQueryable<Tin>, IQueryable<Tout>>> expr) { return (expr.Body as MethodCallExpression).Method; } 

Using:

 var where = GetMethodByExpression<int, int>(q => q.Where((x, idx) => x > 2)); 

Or

 var select = GetMethodByExpression<Person, string>(q => q.Select(x => x.Name)); 
+2
Mar 01 '15 at 15:09
source share

I made a little helper function:

 Func<Type, string, Type[], Type[], MethodInfo> getMethod = (t, n, genargs, args) => { var methods = from m in t.GetMethods() where m.Name == n && m.GetGenericArguments().Length == genargs.Length let mg = m.IsGenericMethodDefinition ? m.MakeGenericMethod(genargs) : m where mg.GetParameters().Select(p => p.ParameterType).SequenceEqual(args) select mg ; return methods.Single(); }; 

Works for simple non-generics:

 var m_movenext = getMethod(typeof(IEnumerator), nameof(IEnumerator.MoveNext), Type.EmptyTypes, Type.EmptyTypes); 

As for complex generics:

 var t_source = typeof(fillin1); var t_target = typeof(fillin2); var m_SelectMany = getMethod( typeof(Enumerable), nameof(Enumerable.SelectMany), new[] { t_source, t_target }, new[] { typeof(IEnumerable<>).MakeGenericType(t_source), typeof(Func<,>).MakeGenericType(t_source, typeof(IEnumerable<>).MakeGenericType(t_target)) }); 
+1
May 11 '16 at 14:41
source share

Chris Moskini's answer is good when you know the method name at compile time. Antamir's answer works if we get the method name at runtime, but rather brute force.

I am using another method for which I received inspiration using a reflector from the Expression.Call .NET function, which selects the correct general method from the string.

 public static MethodInfo GetGenericMethod(Type declaringType, string methodName, Type[] typeArgs, params Type[] argTypes) { foreach (var m in from m in declaringType.GetMethods() where m.Name == methodName && typeArgs.Length == m.GetGenericArguments().Length && argTypes.Length == m.GetParameters().Length select m.MakeGenericMethod(typeArgs)) { if (m.GetParameters().Select((p, i) => p.ParameterType == argTypes[i]).All(x => x == true)) return m; } return null; } 

Using:

 var m = ReflectionUtils.GetGenericMethod(typeof(Queryable), "Where", new[] { typeof(Person) }, typeof(IQueryable<Person>), typeof(Expression<Func<Person, bool>>)); 

If you only need to define a generic method or just don't know the type of T at that time, you can use some dummy types and then shed the general information:

 var m = ReflectionUtils.GetGenericMethod(typeof(Queryable), "Where", new[] { typeof(object) }, typeof(IQueryable<object>), typeof(Expression<Func<object, bool>>)); m = m.GetGenericMethodDefinition(); 
0
May 4 '14 at
source share

Antamir's answer was very useful to me, but it has the error that it does not confirm that the number of parameters in the found method matches the number of types passed when providing a combination of general and specific types.

For example, if you run:

 type.GetMethod("MyMethod",typeof(Refl.T1),typeof(bool)) 

he cannot distinguish between two methods:

 MyMethod<T>(T arg1) MyMethod<T>(T arg1, bool arg2) 

Two calls:

 var p = method.GetParameters(); 

should be changed to:

 var p = method.GetParameters(); if (p.Length != parameters.Length) { correct = false; continue; } 

In addition, both existing break lines must be continue.

0
Feb 11 '15 at 18:11
source share

I figured out the easiest way to use iQuerable expressions when calling a method using reflection. See below code:

You can use the IQuerable expression as required.

 var attributeName = "CarName"; var attributeValue = "Honda Accord"; carList.FirstOrDefault(e => e.GetType().GetProperty(attributeName).GetValue(e, null) as string== attributeValue); 
0
Jan 20 '16 at 0:58
source share



All Articles