用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.TX
types,必须交换“真实”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.Call
的MethodInfo
已经有一个重载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.Call
reflection器获得灵感,该函数从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);