如何使用反射调用扩展方法?

40

我知道类似的问题以前已经有人问过了,但我在以下代码中调用Linq的Where方法时遇到了麻烦。我希望使用反射来动态调用此方法,并动态构建Where子句中使用的委托(或lambda)。这是一个简短的代码示例,一旦工作正常,将有助于形成我正在构建的解释型DSL的一部分。谢谢。

    public static void CallWhereMethod()
    {
        List<MyObject> myObjects = new List<MyObject>(){new MyObject{Name="Jon Simpson"}};
        System.Delegate NameEquals = BuildEqFuncFor<MyObject>("Name", "Jon Simpson");
        object[] atts = new object[1] ;
        atts[0] = NameEquals;

        var ret = typeof(List<MyObject>).InvokeMember("Where", BindingFlags.InvokeMethod, null, InstanceList,atts);
    }

    public static Func<T, bool> BuildEqFuncFor<T>(string prop, object val)
    {
        return t => t.GetType().InvokeMember(prop,BindingFlags.GetProperty,
                                             null,t,null) == val;
    }
6个回答

60
正如其他人所说,扩展方法是编译器的魔法,您可以始终使用VS右键单击,转到定义以找到实现静态方法的真实类型。
从那里开始,它变得相当复杂。Where被重载,因此您需要找到与您想要的签名匹配的实际定义。GetMethod在泛型类型方面有一些限制,因此您必须使用搜索找到实际的一个。
一旦找到方法,您必须使用MakeGenericMethod调用使MethodInfo具体化。
下面是一个完整的工作示例:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace ConsoleApplication9 {
    class Program {

        class MyObject {
            public string Name { get; set; }
        } 

        public static void CallWhereMethod() {
            List<MyObject> myObjects = new List<MyObject>() { 
                new MyObject { Name = "Jon Simpson" },
                new MyObject { Name = "Jeff Atwood" }
            };


            Func<MyObject, bool> NameEquals = BuildEqFuncFor<MyObject>("Name", "Jon Simpson");


            // The Where method lives on the Enumerable type in System.Linq
            var whereMethods = typeof(System.Linq.Enumerable)
                .GetMethods(BindingFlags.Static | BindingFlags.Public)
                .Where(mi => mi.Name == "Where"); 

            Console.WriteLine(whereMethods.Count());
            // 2 (There are 2 methods that are called Where)

            MethodInfo whereMethod = null;
            foreach (var methodInfo in whereMethods) {
                var paramType = methodInfo.GetParameters()[1].ParameterType;
                if (paramType.GetGenericArguments().Count() == 2) {
                    // we are looking for  Func<TSource, bool>, the other has 3
                    whereMethod = methodInfo;
                }
            }

            // we need to specialize it 
            whereMethod = whereMethod.MakeGenericMethod(typeof(MyObject));

            var ret = whereMethod.Invoke(myObjects, new object[] { myObjects, NameEquals }) as IEnumerable<MyObject>;

            foreach (var item in ret) {
                Console.WriteLine(item.Name);
            }
            // outputs "Jon Simpson"

        }

        public static Func<T, bool> BuildEqFuncFor<T>(string prop, object val) {
            return t => t.GetType().InvokeMember(prop, BindingFlags.GetProperty,
                                                 null, t, null) == val;
        }

        static void Main(string[] args) {
            CallWhereMethod();
            Console.ReadKey();

        }
    }
}

感谢您为此付出的努力。非常感激。 - Jon Simpson
那么我想,没有直接的方法让框架选择适当的方法?这就是我正在寻找的,但是找不到任何东西。+1 为解决问题加一。 - Joren
19
我喜欢使用 where 方法来查找 where 方法本身的讽刺意味 :-) - Preet Sangha

10

扩展方法本质上只是静态方法的一种形式。像foo.Frob(参数)这样的扩展方法调用实际上就是SomeClass.Frob(foo, 参数)。在使用Where方法时,你需要引用System.Linq.Enumerable.Where。因此,首先获取Enumerable的类型,然后在该类型上调用Where方法。


我使用以下代码出现了MissingMethodException错误:var ret = typeof(System.Linq.Enumerable).InvokeMember("Where", BindingFlags.InvokeMethod, null, InstanceList, atts);有什么想法吗?谢谢。 - Jon Simpson
问题在于这个方法是通用的。我找到了一篇非常好的博客文章,介绍了如何使用反射调用通用方法 (http://blogs.microsoft.co.il/blogs/gilf/archive/2008/10/10/invoking-generic-methods-with-reflection.aspx),但是还有一个额外的问题:Enumerable.Where 有两个重载,仅在其中一个参数的类型上有所不同(Func<T, bool> 与 Func<T, int, bool>),我还没有找到如何简洁地选择您想要的方法。 - Joren

2

我有些晚了,但如果您需要调用类型未知的IEnumerable的Linq扩展,这可能会对您有所帮助。

IEnumerable<dynamic> test = obj as IEnumerable<dynamic>;

然后,如果不为空,可以测试对象test:

int count = test.Count()

对我来说,这非常有效。


谢谢这个,动态语言改变了游戏规则,我已经改变了我的整个方法! - Jon Simpson

2
这是一个通用情况的答案,其中方法名称是唯一的(因此不同于原始问题所提出的情况,因为Enumerable.Where被重载)。
假设您有一个目标对象targetObject,它是扩展的类型,在类TargetClassExtensions中定义了扩展方法,其扩展方法的名称为ExtensionMethod,接受一个整数参数,并且是泛型的,您想要为类TargetGenericClass调用它。
然后,要通过反射调用此扩展方法,请执行以下操作:
int inputInteger = 9; // Example input for the generic method.

object? result = typeof(TargetClassExtensions)
    .GetMethod(nameof(TargetClassExtensions.ExtensionMethod))
    .MakeGenericMethod(typeof(TargetGenericClass))
    .Invoke(null, new object[] { targetObject, inputInteger });

1

你的代码示例有点令人困惑...除非MyObject是可枚举的。

使用反射,您需要调用System.Linq.Enumerable上的Where方法,将要执行Where操作的可枚举对象传递进去。


非常敏锐,我的问题有错误(现已更新),我尝试过var ret = typeof(System.Linq.Enumerable).InvokeMember("Where", BindingFlags.InvokeMethod, null, InstanceList, atts);但没有成功。有什么想法吗?谢谢。 - Jon Simpson
第一个参数必须是要操作的可枚举对象。第二个参数必须是返回true或false的函数。另外,调用静态函数有点棘手;我已经有一段时间没有这样做了——我建议您调用一个简单的静态函数以确保命令正确。 - user1228

0
扩展方法是C#编译器的一个技巧,它们并不存在于相关类型中。这些特定的扩展方法存在于System.Linq命名空间内的静态类中。我建议在反射器中进行反射,然后对这些类型进行调用。

1
它们是.NET的技巧,而不是C#的技巧。VB.NET也可以创建和使用它们,尽管语法不同,并涉及属性。 - Steven Sudit
它们仍然是编译器技巧,只是VB.NET编译器也使用了相同的技巧。 - ICR

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接