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

Gam*_*lor 34 c# reflection

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

通常这很容易.例如

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)

你只需要你想要的方法,而不需要做任何疯狂的管道,也不需要运行时搜索或使用有风险的字符串.

MethodInfo的

通常在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和参数类型

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

如果您不确定要传入的泛型类型参数,则始终可以在不使用它们的情况下获取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的实例方法示例.

  • 我不知道为什么这不是最好的答案!它简单且类型安全.我把这个用于我们的代码,稍作改动.我在调用非静态方法时必须首先获取泛型定义,然后再进入泛型方法:`var methodInfo = method.Method.GetGenericMethodDefinition().MakeGenericMethod(type1);` (6认同)
  • 我赞同瓦莱丽.这就是我现在这样做的方式.没有错误的字符串给我空引用异常...对于原始问题,需要一个Func <>而不是Action <>,并且必须调用GetGenericMethodDefinition(). (3认同)

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)

  • 这几乎和LINQ中的Ray Tracing示例一样好:) (5认同)

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.T1TSource都会消失; 并且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)


Yur*_*kyy 5

您可能会发现另一个有用的解决方案 - 有可能获得一个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插件中使用了这种方法。你可以在这里查看它是如何工作的