如何使用反射来调用通用方法?

当编译时不知道类型参数,而是在运行时动态获得类型参数时,调用泛型方法的最佳方式是什么?

考虑下面的示例代码 - 在Example()方法中,使用存储在myType变量中的Type调用GenericMethod<T>()的最简洁方法是什么?

public class Sample
{
    public void Example(string typeName)
    {
        Type myType = FindType(typeName);

        // What goes here to call GenericMethod<T>()?
        GenericMethod<myType>(); // This doesn't work

        // What changes to call StaticMethod<T>()?
        Sample.StaticMethod<myType>(); // This also doesn't work
    }

    public void GenericMethod<T>()
    {
        // ...
    }

    public static void StaticMethod<T>()
    {
        //...
    }
}

您需要使用反射来获取开始的方法,然后通过使用MakeGenericMethod提供类型参数来“构建”它:

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

对于静态方法,将null作为第一个参数传递给Invoke 。 这与泛型方法无关 - 这只是普通的反射。

如前所述,使用dynamic的C#4中的很多功能都比较简单 - 当然,如果您可以使用类型推断。 它不适用于类型推断不可用的情况,例如问题中的确切示例。


只是对原始答案的补充。 虽然这将工作:

MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

这也有点危险,因为你失去了GenericMethod编译时检查。 如果稍后进行重构并重命名GenericMethod ,则此代码不会注意,并且在运行时将失败。 另外,如果程序集有任何后处理(例如混淆或删除未使用的方法/类),此代码可能也会中断。

所以,如果你知道你在编译时链接的方法,并且这不会被称为数百万次,所以开销无关紧要,我会将此代码更改为:

Action<> GenMethod = GenericMethod<int>;  //change int by any base type 
                                          //accepted by GenericMethod
MethodInfo method = this.GetType().GetMethod(GenMethod.Method.Name);
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);

虽然不是很漂亮,但您在此处具有对GenericMethod的编译时间引用,如果您重构,删除或使用GenericMethod执行任何操作,此代码将继续工作,或至少在编译时中断(例如,如果您除去GenericMethod )。

其他的做法是创建一个新的包装类,并通过Activator创建它。 我不知道是否有更好的方法。


通过使用dynamic类型而不是反射API,可以大大简化调用带有仅在运行时已知的类型参数的泛型方法。

要使用这种技术,必须从实际对象中知道类型(不仅仅是Type类的一个实例)。 否则,您必须创建该类型的对象或使用标准反射API解决方案。 您可以使用Activator.CreateInstance方法创建一个对象。

如果你想调用一个通用的方法,那么在“正常”使用情况下会有它的类型推断,那么它只是将未知类型的对象转换为dynamic 。 这是一个例子:

class Alpha { }
class Beta { }
class Service
{
    public void Process<T>(T item)
    {
        Console.WriteLine("item.GetType(): " + item.GetType()
                          + "ttypeof(T): " + typeof(T));
    }
}

class Program
{
    static void Main(string[] args)
    {
        var a = new Alpha();
        var b = new Beta();

        var service = new Service();
        service.Process(a); // Same as "service.Process<Alpha>(a)"
        service.Process(b); // Same as "service.Process<Beta>(b)"

        var objects = new object[] { a, b };
        foreach (var o in objects)
        {
            service.Process(o); // Same as "service.Process<object>(o)"
        }
        foreach (var o in objects)
        {
            dynamic dynObj = o;
            service.Process(dynObj); // Or write "service.Process((dynamic)o)"
        }
    }
}

这是这个程序的输出:

item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta
item.GetType(): Alpha    typeof(T): System.Object
item.GetType(): Beta     typeof(T): System.Object
item.GetType(): Alpha    typeof(T): Alpha
item.GetType(): Beta     typeof(T): Beta

Process是一种通用实例方法,它通过使用GetType()方法)和泛型参数的类型(通过使用typeof运算符)写入传入参数的实际类型。

通过将对象参数转换为dynamic类型,我们推迟提供类型参数直到运行时。 当用dynamic参数调用Process方法时,编译器不关心这个参数的类型。 编译器生成的代码在运行时会检查传递参数的实际类型(通过使用反射)并选择要调用的最佳方法。 这里只有这一个泛型方法,所以它被调用一个适当的类型参数。

在这个例子中,输出和你写的一样:

foreach (var o in objects)
{
    MethodInfo method = typeof(Service).GetMethod("Process");
    MethodInfo generic = method.MakeGenericMethod(o.GetType());
    generic.Invoke(service, new object[] { o });
}

具有动态类型的版本绝对更短且更容易编写。 您也不应该担心多次调用此函数的性能。 由于DLR中的缓存机制,下一次调用相同类型的参数应该更快。 当然,您可以编写缓存被调用委托的代码,但通过使用dynamic类型,您可以免费获得此行为。

如果要调用的泛型方法没有参数化类型的参数(因此无法推断它的类型参数),那么可以将泛型方法的调用封装到辅助方法中,如下例所示:

class Program
{
    static void Main(string[] args)
    {
        object obj = new Alpha();

        Helper((dynamic)obj);
    }

    public static void Helper<T>(T obj)
    {
        GenericMethod<T>();
    }

    public static void GenericMethod<T>()
    {
        Console.WriteLine("GenericMethod<" + typeof(T) + ">");
    }
}

增加型号安全性

使用dynamic对象代替使用反射API的真正好处在于,只有在运行时才会知道这种特定类型的编译时检查失败。 像往常一样,编译器会静态分析其他参数和方法的名称。 如果您删除或添加更多参数,请更改其类型或重命名方法名称,则会出现编译时错误。 如果将方法名称作为Type.GetMethod的字符串提供,并将参数作为MethodInfo.Invoke的对象数组提供,则不会发生这种情况。

下面是一个简单的例子,说明在编译时(注释代码)和其他运行时如何捕获一些错误。 它还显示了DLR如何尝试解析要调用的方法。

interface IItem { }
class FooItem : IItem { }
class BarItem : IItem { }
class Alpha { }

class Program
{
    static void Main(string[] args)
    {
        var objects = new object[] { new FooItem(), new BarItem(), new Alpha() };
        for (int i = 0; i < objects.Length; i++)
        {
            ProcessItem((dynamic)objects[i], "test" + i, i);

            //ProcesItm((dynamic)objects[i], "test" + i, i);
            //compiler error: The name 'ProcesItm' does not
            //exist in the current context

            //ProcessItem((dynamic)objects[i], "test" + i);
            //error: No overload for method 'ProcessItem' takes 2 arguments
        }
    }

    static string ProcessItem<T>(T item, string text, int number)
        where T : IItem
    {
        Console.WriteLine("Generic ProcessItem<{0}>, text {1}, number:{2}",
                          typeof(T), text, number);
        return "OK";
    }
    static void ProcessItem(BarItem item, string text, int number)
    {
        Console.WriteLine("ProcessItem with Bar, " + text + ", " + number);
    }
}

这里我们再次通过将参数转换为dynamic类型来执行一些方法。 只有第一个参数类型的验证被推迟到运行时。 如果您调用的方法的名称不存在,或者其他参数无效(参数的错误数量或错误的类型),您将收到编译器错误。

当你将dynamic参数传递给一个方法时,这个调用是最近被绑定的。 方法重载解析在运行时发生,并尝试选择最佳的重载。 因此,如果您使用BarItem类型的对象调用ProcessItem方法,那么您实际上会调用非泛型方法,因为它与此类型更匹配。 然而,当你传递一个Alpha类型的参数时,你会得到一个运行时错误,因为没有可以处理这个对象的方法(一个泛型方法有where T : IItemAlpha类没有实现这个接口的约束)。 但是,这是整个问题。 编译器没有这个调用有效的信息。 你作为程序员知道这一点,你应该确保这段代码运行时没有错误。

返回类型陷阱

当您使用动态类型的参数调用非void方法时,其返回类型可能也是dynamic 。 所以如果你想改变以前的例子到这个代码:

var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

那么结果对象的类型将是dynamic 。 这是因为编译器并不总是知道将调用哪个方法。 如果你知道函数调用的返回类型,那么你应该隐式地将它转换为所需的类型,以便剩下的代码是静态类型的:

string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);

如果类型不匹配,您将得到一个运行时错误。

实际上,如果你尝试在前面的例子中得到结果值,那么你将在第二次循环迭代中得到一个运行时错误。 这是因为你试图保存一个void函数的返回值。

链接地址: http://www.djcxy.com/p/14151.html

上一篇: How do I use reflection to call a generic method?

下一篇: Virtual member call in a constructor