用reflectionselect正确的通用方法

我想通过reflectionselect正确的通用方法,然后调用它。

通常这很容易。 例如

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

然而,当方法有不同的generics重载时,问题就开始了。 例如System.Linq.Queryable类中的静态方法。 “Where”方法有两个定义

 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) 

这个我GetGethod不起作用,因为它不能两个都去追求。 所以我想select正确的一个。

到目前为止,我经常采取第一种或第二种方法,这取决于我的需要。 喜欢这个:

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

但是我对此并不满意,因为我假定第一种方法是正确的。 我宁可想要通过参数typesfind正确的方法。 但我不知道如何。

我试着通过“types”,但没有奏效。

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

所以有谁有一个想法,我怎么能通过reflectionfind“正确”的通用方法。 例如Queryable类的“Where”方法的正确版本?

这可以做,但不是很漂亮!

例如,要获得你的问题Where提到的第一个重载,你可以这样做:

 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(); 

或者如果你想要第二个重载:

 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(); 

在编译时你可以稍微优雅地select一个方法的特定的generics重载,而不会像任何其他答案那样将任何string传递给运行时search。

静态方法

假设你有多个相同名称的静态方法,如:

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

如果您创build的Action或Func与您正在查找的重载的通用计数和参数计数相匹配,则可以在编译时使用相对较less的杂技来select它。

例子:select第一个方法 – 返回void,所以使用一个Action,取一个generics。 我们使用object来避免指定types:

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

例子:select第二种方法 – 返回void,所以Action,2个genericstypes,所以使用type对象两次,一次为每个2个generics参数:

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

你只是得到了你想要的方法,而没有做任何疯狂的pipe道工作,没有运行时间search或使用风险string。

MethodInfo的

通常在Reflection中,您需要MethodInfo对象,您也可以以编译安全的方式获取该对象。 这是当你传递你想要在你的方法中使用的实际genericstypes。 假设你想要上面的第二种方法:

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

有没有任何reflectionsearch或调用GetMethod()或脆弱的string的generics方法。

静态扩展方法

你用Queryable引用的具体例子。在重载的情况下,你会在Func定义中产生一些幻想,但通常遵循相同的模式。 最常用的Where()扩展方法的签名是:

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

显然这会稍微复杂 – 在这里是:

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

实例方法

结合瓦莱丽的评论 – 获得一个实例方法,你需要做一些非常相似的事情。 假设你在你的类中有这个实例方法:

 public void MyMethod<T1>(T1 thing) 

首先select与静态方法相同的方法:

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

然后调用GetGenericMethodDefinition()到通用的MethodInfo,最后通过MakeGenericMethod()传递你的types。

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

解耦MethodInfo和参数types

这个问题没有被要求,但是一旦你做了上述的事情,你可能会发现自己在一个地方select了方法,并决定什么types在另一个地方通过。 你可以分解这两个步骤。

如果您不确定要传入的genericstypes参数,则可以始终获取MethodInfo对象。

静态的:

 var methodInfo = method.Method; 

例如:

 var methodInfo = method.Method.GetGenericMethodDefinition(); 

然后将其传递给其他一些知道要实例化的types并调用该方法的方法,例如:

 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 }); } 

有一点特别有用,就是从类内部select一个类的特定实例方法,然后将这个方法暴露给以后需要使用各种types的外部调用者。

编辑:清理解释,并入Valerie的实例方法示例。

这个问题大约2岁,但我想出了(我认为是)一个优雅的解决scheme,并认为我会与在这里在StackOverflow的罚款人分享。 希望这将有助于那些通过各种search查询到达这里。

正如海报所说,问题是要得到正确的通用方法。 例如,一个LINQ扩展方法可能有大量的重载,并且嵌套在其他genericstypes中的types参数都被用作参数。 我想要做这样的事情:

 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>> ); 

正如你所看到的,我创build了一些存根types“T1”和“T2”,类“Refl”中的嵌套类(一个静态类,它包含我所有的各种reflection实用程序扩展函数等等。它们作为占位符types参数将正常运行。上面的例子分别对应于获取以下LINQ方法:

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

所以应该清楚的是Refl.T1去往TSource将会去的地方, Refl.T2表示TKey参数TX类声明如下:

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

使用三个TX类,您的代码可以识别最多包含三个genericstypes参数的方法。

接下来的一点就是实现通过GetMethods()进行search的函数:

 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's 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; } 

上面的代码完成了大部分的工作,它遍历一个特定types的所有方法,并将它们与给定的参数types进行比较来search。 可是等等! 那“替代”function呢? 这是一个很好的小recursion函数,它将search整个参数types树 – 毕竟,一个参数types本身可以是一个genericstypes,它可能包含Refl.TXtypes,必须交换“真实”types参数这些对我们来说是隐藏的。

 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; } 

让编译器为你做:

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

对于带有索引的Where,或者只是在Whereexpression式中省略第二个参数

使用DynamicMethods.GenericMethodInvokerMethod ,GetMethod 不足以使用generics

另一个解决scheme,你可能会发现有用的 – 有可能得到一个基于Expression.CallMethodInfo已经有一个重载parsing的逻辑。

例如,如果您需要获得一些特定的Enumerable.Where方法,可以使用下面的代码来完成:

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

示例中的第三个参数 – 描述generics参数的types,以及所有其他参数 – 参数的types。

以同样的方式,甚至可以获得非静态对象generics方法。您只需要将第一个参数从typeof (YourClass)更改为Expression.Default(typeof (YourClass))

实际上,我在.NET Reflection API 插件中使用了这种方法。 你可以检查它是如何工作的

我做了一个小帮手func:

 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(); }; 

适用于简单的非generics:

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

就像复杂的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)) }); 

除了@ MBOROS的回答。

你可以避免使用这个辅助方法来编写复杂的generics参数:

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

用法:

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

要么

 var select = GetMethodByExpression<Person, string>(q => q.Select(x => x.Name)); 

Chris Moschini在编译时知道方法名称的答案是很好的。 如果我们在运行时获得方法名称,Antamir的答案是有效的,但是相当有趣。

我正在使用另一种方式,为此,我使用.NET函数Expression.Callreflection器获得灵感,该函数从string中select正确的通用方法。

 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; } 

用法:

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

如果您只需要generics方法定义或者根本不知道当前的typesT ,则可以使用一些伪造types,然后去除generics信息:

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

Antamir的答案对我来说是非常有用的,但是它有一个错误,就是它没有validation当你提供generics和具体types的混合时,find的方法的参数个数与传入的types数量匹配。

例如,如果您运行:

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

它不能区分两种方法:

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

两个电话:

 var p = method.GetParameters(); 

应改为:

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

而且,现有的“rest”线应该是“继续的”。

我发现使用reflection调用方法时使用iQuerableexpression式的最简单的方法。 请看下面的代码:

您可以根据需要使用IQuerableexpression式。

 var attributeName = "CarName"; var attributeValue = "Honda Accord"; carList.FirstOrDefault(e => e.GetType().GetProperty(attributeName).GetValue(e, null) as string== attributeValue);