选择具有反射的右通用方法

时间:2010-09-02 21:45:49

标签: c# reflection

我想通过反射选择正确的通用方法然后调用它。

通常这很容易。例如

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

然而,当该方法存在不同的泛型重载时,问题就开始了。例如,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)

这说明GetMethod无法正常工作,因为它无法让两者消失。因此,我想选择正确的。

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

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

但是我对此并不满意,因为我做了一个很大的假设,即第一种方法是正确的。我宁愿通过参数类型找到正确的方法。但我无法弄清楚如何。

我尝试传递'类型',但它没有用。

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

所以有人知道如何通过反射找到'正确'的通用方法。例如,Queryable-class上的'Where'方法的正确版本?

13 个答案:

答案 0 :(得分:38)

您可以在编译时优雅地选择方法的特定泛型重载,而不将任何字符串传递给运行时搜索,就像其他答案一样。

静态方法

假设您有多个同名的静态方法,如:

public static void DoSomething<TModel>(TModel model)

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

// etc

如果您创建的Action或Func与您要查找的重载的通用计数和参数计数相匹配,则可以在编译时选择相对较少的杂技。

示例:选择第一个方法 - 返回void,因此使用Action,取一个泛型。我们使用object来避免指定类型:

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

示例:选择第二个方法 - 返回void,因此Action,2个泛型类型因此使用type对象两次,对于2个通用参数中的每一个使用一次:

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

你只是得到了你想要的方法而没有做任何疯狂的管道,也没有运行时搜索或使用有风险的字符串。

MethodInfo的

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

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

你的通用方法没有任何反射搜索或调用GetMethod()或脆弱的字符串。

静态扩展方法

你引用Queryable.Where重载的具体例子迫使你在Func定义中有点花哨,但通常遵循相同的模式。 The signature for the most commonly used Where() extension method是:

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

实例方法

结合Valerie的评论 - 获取实例方法,您需要做一些非常相似的事情。假设你的班级中有这个实例方法:

public void MyMethod<T1>(T1 thing)

首先选择与静力学相同的方法:

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

然后调用GetGenericMethodDefinition()以获取通用MethodInfo,最后使用MakeGenericMethod()传递您的类型:

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

解耦MethodInfo和参数类型

问题中没有要求这样做,但是一旦你完成上述操作,你可能会发现自己在一个地方选择了这个方法,并决定在另一个地方传递它的类型。你可以解开这两个步骤。

如果您不确定要传入的泛型类型参数,您可以随时获取没有它们的MethodInfo对象。

静态:

var methodInfo = method.Method;

实例:

var methodInfo = method.Method.GetGenericMethodDefinition();

并将其传递给其他知道要实例化的类型的方法,并使用 - 例如:

调用该方法
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 });
}

这一点特别有用的是从类中选择一个类的特定实例方法,然后将其暴露给以后需要它的各种类型的外部调用者。

编辑:清理说明,纳入了Valerie的实例方法示例。

答案 1 :(得分:21)

可以做到,但它并不漂亮!

例如,要获得问题中提及的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();

或者如果您想要第二次重载:

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

答案 2 :(得分:18)

这个问题大约有2年了,但我提出了(我认为是)优雅的解决方案,并且认为我会在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>>
);

正如你所看到的,我创建了一些存根类型“T1”和“T2”,嵌套类在一个类“Refl”中(一个包含我所有各种Reflection实用程序扩展函数的静态类,等等。它们用作类型参数通常所在的占位符。上面的示例分别对应于获取以下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类,您的代码可以识别包含最多三个泛型类型参数的方法。

下一步的魔力是实现通过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;
}

上面的代码完成了大部分工作 - 它遍历特定​​类型的所有方法,并将它们与要搜索的给定参数类型进行比较。可是等等!那个“替代”功能怎么样?这是一个很好的小递归函数,它将搜索整个参数类型树 - 毕竟,参数类型本身可以是泛型类型,它可能包含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;
}

答案 3 :(得分:4)

您可能会觉得有用的另一个解决方案 - 可以基于已经具有重载解析逻辑的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;

示例中的第三个参数 - 描述泛型参数的类型,以及所有其他参数 - 参数类型。

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

实际上,我在plugin中使用了.NET Reflection API的方法。您可以查看它的工作原理here

答案 4 :(得分:3)

让编译器为您完成:

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

表示Where with index,或者只是在没有

的Where表达式中省略第二个参数

答案 5 :(得分:2)

使用DynamicMethods.GenericMethodInvokerMethod,GetMethod is not enough与generics一起使用

答案 6 :(得分:2)

除了@ MBoros的回答。

您可以使用此辅助方法避免编写复杂的泛型参数:

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

答案 7 :(得分:1)

我做了一个小助手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();
    };

适用于简单的非泛型:

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

喜欢复杂的泛型:

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

答案 8 :(得分:1)

我有一个类似的问题,我想我应该在这里发布我的解决方案。我正在尝试调用几个函数:

p.Foo<Klass1>(true)
p.Foo<Klass2>(true)
p.Foo<Klass3>(true)
bool k1 = p.Bar<Klass1>()
bool k2 = p.Bar<Klass2>()
bool k3 = p.Bar<Klass3>()

我的解决方案:

public static TAction RemapGenericMember<TAction>(object parent, Type target, TAction func) where TAction : Delegate { 
    var genericMethod = func?.Method?.GetGenericMethodDefinition()?.MakeGenericMethod(target);
    if (genericMethod.IsNull()) {
        throw new Exception($"Failed to build generic call for '{func.Method.Name}' with generic type '{target.Name}' for parent '{parent.GetType()}'");
    }
    return (TAction)genericMethod.CreateDelegate(typeof(TAction), parent);
}

现在我可以打电话给

foreach(var type in supportedTypes) {
   InvokeGenericMember<Action<bool>>(p, type, Foo<object>)(true);
   bool x = InvokeGenericMember<Function<bool>>(p, type, Bar<object>)();
}

答案 9 :(得分:0)

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

我正在使用另一种方式,我使用.NET函数Expression.Call的反射器获得灵感,它从字符串中选择正确的泛型方法。

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

如果您只需要通用的方法定义或者当时根本不知道类型T,您可以使用一些伪类型,然后去除通用的信息:

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

答案 10 :(得分:0)

Antamir的答案对我来说非常有用,但它有一个错误,即它没有验证找到的方法上的参数数量与您提供混合时传入的类型数量相匹配通用和具体类型。

例如,如果您运行:

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

此外,现有的&#39;破坏&#39;行应该继续&#39;

答案 11 :(得分:0)

我在使用反射调用方法时发现了使用iQuerable表达式的最简单方法。请参阅以下代码:

您可以根据要求使用IQuerable表达式。

var attributeName = "CarName";
var attributeValue = "Honda Accord";

carList.FirstOrDefault(e => e.GetType().GetProperty(attributeName).GetValue(e, null) as string== attributeValue);

答案 12 :(得分:0)

var firstGenericParam = Type.MakeGenericMethodParameter(0);
var firstParam = typeof(IQueryable<>).MakeGenericType(firstGenericParam);
var funcType = typeof(Func<,>).MakeGenericType(firstGenericParam, typeof(bool));
//var funcType = typeof(Func<,,>).MakeGenericType(firstGenericParam, typeof(int), typeof(bool)); //for second version 
var secondParam = typeof(Expression<>).MakeGenericType(funcType);
var method = typeof(Queryable).GetMethod(nameof(Queryable.Where), new Type[] { firstParam, secondParam });