我想通过反射选择正确的通用方法,然后调用它.
通常这很容易.例如
var method = typeof(MyType).GetMethod("TheMethod");
var typedMethod = method.MakeGenericMethod(theTypeToInstantiate);
Run Code Online (Sandbox Code Playgroud)
但是,当方法存在不同的泛型重载时,问题就开始了.例如,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)
Run Code Online (Sandbox Code Playgroud)
这说明GetMethod无法正常工作,因为它无法让两者黯然失色.因此,我想选择正确的.
到目前为止,我经常只采取第一种或第二种方法,这取决于我的需要.像这样:
var method = typeof (Queryable).GetMethods().First(m => m.Name == "Where");
var typedMethod = method.MakeGenericMethod(theTypeToInstantiate);
Run Code Online (Sandbox Code Playgroud)
但是我对此并不满意,因为我做了一个很大的假设,即第一种方法是正确的.我宁愿通过参数类型找到正确的方法.但我无法弄清楚如何.
我尝试传递'类型',但它没有用.
var method = typeof (Queryable).GetMethod(
"Where", BindingFlags.Static,
null,
new Type[] {typeof (IQueryable<T>), typeof (Expression<Func<T, bool>>)},
null);
Run Code Online (Sandbox Code Playgroud)
所以有人知道如何通过反射找到'正确'的通用方法.例如,Queryable类的'Where'方法的正确版本?
Chr*_*ini 42
您可以在编译时优雅地选择方法的特定泛型重载,而不将任何字符串传递给运行时搜索,就像这里的其他答案一样.
假设您有多个同名的静态方法,例如:
public static void DoSomething<TModel>(TModel model)
public static void DoSomething<TViewModel, TModel>(TViewModel viewModel, TModel model)
// etc
Run Code Online (Sandbox Code Playgroud)
如果您创建的Action或Func与您要查找的重载的泛型计数和参数计数相匹配,则可以在编译时使用相对较少的杂技来选择它.
示例:选择第一个方法 - 返回void,因此使用Action,取一个泛型.我们使用object来避免指定类型:
var method = new Action<object>(MyClass.DoSomething<object>);
Run Code Online (Sandbox Code Playgroud)
示例:选择第二个方法 - 返回void,因此Action,2个泛型类型因此使用type对象两次,对于2个通用参数中的每一个使用一次:
var method = new Action<object, object>(MyClass.DoSomething<object, object>);
Run Code Online (Sandbox Code Playgroud)
你只需要你想要的方法,而不需要做任何疯狂的管道,也不需要运行时搜索或使用有风险的字符串.
通常在Reflection中,您需要MethodInfo对象,您也可以以编译安全的方式获取该对象.这是当您传递要在方法中使用的实际泛型类型时.假设您想要上面的第二种方法:
var methodInfo = method.Method.MakeGenericMethod(type1, type2);
Run Code Online (Sandbox Code Playgroud)
你的泛型方法没有任何反射搜索或调用GetMethod()或脆弱的字符串.
你引用Queryable.Where重载的具体例子迫使你在Func定义中有点花哨,但通常遵循相同的模式.最常用的Where()扩展方法的签名是:
public static IQueryable<TModel> Where<TModel>(this IQueryable<TModel>, Expression<Func<TModel, bool>>)
Run Code Online (Sandbox Code Playgroud)
显然这会稍微复杂一点 - 这里是:
var method = new Func<IQueryable<object>,
Expression<Func<object, bool>>,
IQueryable<object>>(Queryable.Where<object>);
var methodInfo = method.Method.MakeGenericMethod(modelType);
Run Code Online (Sandbox Code Playgroud)
结合Valerie的评论 - 要获得实例方法,您需要做一些非常相似的事情.假设您的类中有此实例方法:
public void MyMethod<T1>(T1 thing)
Run Code Online (Sandbox Code Playgroud)
首先选择与静态相同的方法:
var method = new Action<object>(MyMethod<object>);
Run Code Online (Sandbox Code Playgroud)
然后调用GetGenericMethodDefinition()以获取泛型MethodInfo,最后传递您的类型MakeGenericMethod():
var methodInfo = method.Method.GetGenericMethodDefinition().MakeGenericMethod(type1);
Run Code Online (Sandbox Code Playgroud)
问题中没有要求这样做,但是一旦你完成上述操作,你可能会发现自己在一个地方选择了这个方法,并决定在另一个地方传递它的类型.你可以解开这两个步骤.
如果您不确定要传入的泛型类型参数,则始终可以在不使用它们的情况下获取MethodInfo对象.
静态的:
var methodInfo = method.Method;
Run Code Online (Sandbox Code Playgroud)
例如:
var methodInfo = method.Method.GetGenericMethodDefinition();
Run Code Online (Sandbox Code Playgroud)
并将其传递给其他一些知道它想要实例化的类型的方法并调用方法 - 例如:
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 });
}
Run Code Online (Sandbox Code Playgroud)
这特别有用的一件事是从类内部选择一个类的特定实例方法,然后将其暴露给以后需要它的各种类型的外部调用者.
编辑:清理解释,合并了Valerie的实例方法示例.
Luk*_*keH 23
它可以做到,但它不漂亮!
例如,要获得Where问题中提到的第一个重载,您可以这样做:
var where1 = typeof(Queryable).GetMethods()
.Where(x => x.Name == "Where")
.Select(x => new { M = x, P = x.GetParameters() })
.Where(x => x.P.Length == 2
&& x.P[0].ParameterType.IsGenericType
&& x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& x.P[1].ParameterType.IsGenericType
&& x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
.Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
.Where(x => x.A[0].IsGenericType
&& x.A[0].GetGenericTypeDefinition() == typeof(Func<,>))
.Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
.Where(x => x.A[0].IsGenericParameter
&& x.A[1] == typeof(bool))
.Select(x => x.M)
.SingleOrDefault();
Run Code Online (Sandbox Code Playgroud)
或者如果你想要第二次重载:
var where2 = typeof(Queryable).GetMethods()
.Where(x => x.Name == "Where")
.Select(x => new { M = x, P = x.GetParameters() })
.Where(x => x.P.Length == 2
&& x.P[0].ParameterType.IsGenericType
&& x.P[0].ParameterType.GetGenericTypeDefinition() == typeof(IQueryable<>)
&& x.P[1].ParameterType.IsGenericType
&& x.P[1].ParameterType.GetGenericTypeDefinition() == typeof(Expression<>))
.Select(x => new { x.M, A = x.P[1].ParameterType.GetGenericArguments() })
.Where(x => x.A[0].IsGenericType
&& x.A[0].GetGenericTypeDefinition() == typeof(Func<,,>))
.Select(x => new { x.M, A = x.A[0].GetGenericArguments() })
.Where(x => x.A[0].IsGenericParameter
&& x.A[1] == typeof(int)
&& x.A[2] == typeof(bool))
.Select(x => x.M)
.SingleOrDefault();
Run Code Online (Sandbox Code Playgroud)
ata*_*mir 18
这个问题大约有两年了,但我想出了一个优雅的解决方案(并且我想到了),并且认为我会在StackOverflow上与优秀人员分享.希望它能帮助那些通过各种搜索查询到达这里的人.
正如海报所说,问题在于获得正确的通用方法.例如,LINQ扩展方法可能有大量的重载,其中类型参数嵌套在其他泛型类型中,所有这些都用作参数.我想做这样的事情:
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>>
);
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我创建了一些存根类型"T1"和"T2",嵌套类在"Refl"类(一个包含我所有各种Reflection实用程序扩展函数的静态类等)中.它们用作占位符通常会有类型参数.上面的例子分别对应于获取以下LINQ方法:
Enumerable.Where(IQueryable<TSource> source, Func<TSource, bool> predicate);
Enumerable.GroupBy(IQueryable<Source> source, Func<TSource, TKey> selector);
Run Code Online (Sandbox Code Playgroud)
所以应该清楚的是,在这两个电话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.
}
Run Code Online (Sandbox Code Playgroud)
使用三个TX类,您的代码可以识别包含最多三个泛型类型参数的方法.
下一步的魔力是通过以下方式实现搜索功能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'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;
}
Run Code Online (Sandbox Code Playgroud)
上面的代码完成了大部分工作 - 它遍历特定类型的所有方法,并将它们与要搜索的给定参数类型进行比较.可是等等!那个"替代"功能怎么样?这是一个很好的小递归函数,它将搜索整个参数类型树 - 毕竟,参数类型本身可以是泛型类型,它可能包含Refl.TX类型,必须交换为隐藏的"真实"类型参数来自我们
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;
}
Run Code Online (Sandbox Code Playgroud)
您可能会发现另一个有用的解决方案 - 有可能获得一个MethodInfo基于Expression.Call已经具有重载解析逻辑的解决方案。
例如,如果您需要获取一些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;
Run Code Online (Sandbox Code Playgroud)
示例中的第三个参数 - 描述通用参数的类型,以及所有其他参数 - 参数类型。
以同样的方式,甚至可以获取非静态对象通用方法。您只需要将第一个参数从 更改typeof (YourClass)为Expression.Default(typeof (YourClass))。
实际上,我已经在我的 .NET 反射 API插件中使用了这种方法。你可以在这里查看它是如何工作的
| 归档时间: |
|
| 查看次数: |
14974 次 |
| 最近记录: |