C# 如何使用反射调用泛型方法?

C# 如何使用反射调用泛型方法?,c#,.net,generics,reflection,C#,.net,Generics,Reflection,当类型参数在编译时未知,但在运行时动态获取时,调用泛型方法的最佳方法是什么 考虑以下示例代码-在示例方法中,使用myType变量中存储的类型调用GenericMethod的最简洁的方法是什么 public class Sample { public void Example(string typeName) { Type myType = FindType(typeName); // What goes here to call GenericM

当类型参数在编译时未知,但在运行时动态获取时,调用泛型方法的最佳方法是什么

考虑以下示例代码-在示例方法中,使用myType变量中存储的类型调用GenericMethod的最简洁的方法是什么

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>()
    {
        //...
    }
}

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

对于静态方法,传递null作为要调用的第一个参数。这与泛型方法无关——它只是普通的反射


如前所述,与使用动态的C4相比,这要简单得多——当然,如果您可以使用类型推断的话。在类型推断不可用的情况下,例如问题中的确切示例,它没有帮助。

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

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执行任何操作,该代码将继续工作,或者至少在编译时中断(例如,如果您删除GenericMethod)


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

对于C4.0,反射是不必要的,因为DLR可以使用运行时类型来调用它。由于动态地使用DLR库而不是C编译器为您生成代码是一种痛苦,因此开源框架.net标准1.5为您提供了对编译器将为您生成的相同调用的轻松缓存运行时访问

var name = InvokeMemberName.Create;
Dynamic.InvokeMemberAction(this, name("GenericMethod", new[]{myType}));


var staticContext = InvokeContext.CreateStatic;
Dynamic.InvokeMemberAction(staticContext(typeof(Sample)), name("StaticMethod", new[]{myType}));

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

要使用此技术,必须从实际对象中知道类型,而不仅仅是类型类的实例。否则,必须创建该类型的对象或使用标准反射API。可以使用该方法创建对象

如果您想调用一个泛型方法,在正常使用中,该方法会推断出其类型,那么它只需要将未知类型的对象强制转换为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)"
        }
    }
}
Process是一个泛型实例方法,它使用GetType方法写入传递参数的实际类型,使用typeof运算符写入泛型参数的类型

通过将对象参数强制转换为动态类型,我们将提供类型参数推迟到运行时。当使用动态参数调用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中的机制,具有相同类型参数的下一个调用应该更快。当然,您可以编写缓存调用的委托的代码,但是通过使用动态类型,您可以免费获得这种行为

如果要调用的泛型方法没有参数化类型的参数,因此无法推断其类型参数,则可以将泛型方法的调用封装在助手方法中,如以下示例所示:

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) + ">");
    }
}
这里,我们通过将参数强制转换为动态类型,再次执行一些方法。只有第一个参数类型的验证推迟到运行时。如果您正在调用的方法的名称不存在,或者如果其他参数无效,或者参数数量错误或类型错误,则会出现编译器错误

当您将动态参数传递给一个方法时,此调用将被终止。方法重载解析发生在运行时,并尝试选择最佳重载。因此,如果您使用BarItem类型的对象调用ProcessItem方法,那么实际上您将调用非泛型方法,因为它更适合此类型。但是,当您传递Alpha类型的参数时,会出现运行时错误,因为没有方法可以处理此对象。泛型方法具有约束,其中T:IItem和Alpha类不实现此接口。但这就是重点。公司 iler没有此调用有效的信息。作为一名程序员,您知道这一点,并且应该确保此代码运行时没有错误

返回类型:gotcha 当您使用动态类型的参数调用非void方法时,其返回类型可能会改变。因此,如果您将前面的示例更改为以下代码:

var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
那么结果对象的类型将是动态的。这是因为编译器并不总是知道将调用哪个方法。如果知道函数调用的返回类型,则应将其转换为所需类型,以便代码的其余部分为静态类型:

string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
如果类型不匹配,将出现运行时错误

实际上,如果您在上一个示例中尝试获取结果值,那么在第二个循环迭代中会出现运行时错误。这是因为您试图保存void函数的返回值。

添加到:

从类型信息调用泛型方法涉及三个步骤

TLDR:使用类型对象调用已知泛型方法可以通过以下方式完成: 方法2:创建一个委托,获取MethodInfo对象,然后调用GetGenericMethodDefinition 从包含方法的类内部:

MethodInfo method = ((Action)GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();
MethodInfo method = ((Action)(new Sample())
    .GenericMethod<object>)
    .Method
    .GetGenericMethodDefinition();

MethodInfo method = ((Action)Sample.StaticMethod<object>)
    .Method
    .GetGenericMethodDefinition();
从方法中获取泛型方法定义

MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();
步骤2调用MakeGenericMethod以创建具有适当类型的泛型方法。 第3步是使用适当的参数调用该方法。
这是我的2美分基于,但有两个参数需要一个通用的方法

假设您的方法在Helpers类中定义如下:

public class Helpers
{
    public static U ConvertCsvDataToCollection<U, T>(string csvData)
    where U : ObservableCollection<T>
    {
      //transform code here
    }
}
然后调用GetMethod以查找泛型函数:

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
到目前为止,上面的调用与上面解释的调用几乎相同,但在需要向其传递多个参数时有一点不同

您需要将类型[]数组传递给MakeGenericMethod函数,该函数包含上面创建的伪对象类型:

MethodInfo generic = method.MakeGenericMethod(
new Type[] {
   myCollection.GetType(),
   myObject.GetType()
});
完成后,您需要调用上面提到的Invoke方法

generic.Invoke(null, new object[] { csvData });
你完成了。真有魅力

更新:

正如@Bevan所强调的,调用MakeGenericMethod函数时,我不需要创建数组,因为它接受参数,也不需要创建对象来获取类型,因为我可以直接将类型传递给该函数。在我的例子中,由于我在另一个类中预定义了类型,我只需将代码更改为:

object myCollection = null;

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

MethodInfo generic = method.MakeGenericMethod(
   myClassInfo.CollectionType,
   myClassInfo.ObjectType
);

myCollection = generic.Invoke(null, new object[] { csvData });
myClassInfo包含2个类型为type的属性,我在运行时根据传递给构造函数的枚举值设置这些属性,并将为我提供相关类型,然后在MakeGenericMethod中使用这些类型

再次感谢您突出显示这个@Bevan。

没有人提供经典的反射解决方案,因此下面是一个完整的代码示例:

当执行上述控制台应用程序时,我们得到了正确的预期结果:

Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3
灵感来自-假设您有两个或更多的类,如

public class Bar { }
public class Square { }
您希望使用Bar和Square调用方法Foo,该方法声明为

public class myClass
{
    public void Foo<T>(T item)
    {
        Console.WriteLine(typeof(T).Name);
    }
}
这对每个班级都有效。在这种情况下,它将输出:

方格 酒吧


在使用反射调用方法的情况下,通常方法名本身由另一个方法发现。事先知道方法名称并不常见。我同意反射的常见用法。但最初的问题是如何调用GenericMethod如果语法允许,我们根本不需要GetMethod。但是对于这个问题,我该如何编写GenericMethod呢?我认为答案应该包括一种避免丢失GenericMethod的编译时链接的方法。现在,我不知道这个问题是否常见,但我确实知道我昨天遇到了这个问题,这就是为什么我要问这个问题。你可以用GenMethod.Method.GetGenericMethodDefinition代替这个。GetType.GetMethodGenMethod.Method.Name。它稍微更干净,可能更安全。在您的示例中myType是什么意思?现在您可以使用nameofGenericMethod+1;请注意,默认情况下GetMethod只考虑公共实例方法,因此,您可能需要BindingFlags.Static和/或BindingFlags.NonPublic。正确的标志组合是BindingFlags.NonPublic | BindingFlags.Instance和可选的BindingFlags.Static。一个被标记为dupe的问题想知道如何使用静态方法实现这一点,从技术上讲,这里的问题也是如此。调用静态方法时,Invoke的第一个参数应为null。第一个参数只有在调用实例方法时才是必需的。@ChrisMoschini:在答案中添加了它。@gzou:我在答案中添加了一些内容-但是请注意,对于调用问题中的泛型方法,dynamic没有帮助,因为类型推断不可用。编译器无法使用任何参数来确定类型参数。我尝试了Jon的解决方案,直到我在类中公开了泛型方法,它才能够工作。我知道另一个Jon回答说您需要指定bindingflags,但这没有帮助。您还需要BindingFl
ags.Instance,而不仅仅是BindingFlags.NonPublic,以获取私有/内部方法。这个问题的现代版本:@Peter Mortensen-fyi在“?”之前使用空格来分隔英语部分和非英语C部分;我不知道是谁把空间移走了,让它看起来像?是代码的一部分。如果没有代码,我当然同意删除空格,但在这种情况下…我们可以定义一个泛型方法,然后使用GetMethod方法获取泛型方法的所有信息并使用它,当您传递Alpha类型的参数时,将出现运行时错误,因为没有方法可以处理此对象。如果我调用var a=new Alpha ProcessItema,test+I,那么为什么通用ProcessItem方法不能有效地处理这个问题,并输出通用的流程项?@AlexEdelstein我编辑了我的答案以澄清一点。这是因为泛型ProcessItem方法具有泛型约束,并且只接受实现IItem接口的对象。当您调用ProcessItemnew Aplha时,test,1;或ProcessItemobjectnew Aplha,test,1;你会得到一个编译器错误,但当转换为dynamic时,你会将检查推迟到运行时。很好的答案和解释,对我来说非常合适。比公认的答案好得多,编写时间更短,性能更高,更安全;也不需要创建实例来获取类型-methodInfo.MakeGenericMethodTypeOfCollection,TypeOfObject就足够了。
MethodInfo generic = method.MakeGenericMethod(myType);
generic.Invoke(this, null);
public class Helpers
{
    public static U ConvertCsvDataToCollection<U, T>(string csvData)
    where U : ObservableCollection<T>
    {
      //transform code here
    }
}
object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
MethodInfo generic = method.MakeGenericMethod(
new Type[] {
   myCollection.GetType(),
   myObject.GetType()
});
generic.Invoke(null, new object[] { csvData });
object myCollection = null;

MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");

MethodInfo generic = method.MakeGenericMethod(
   myClassInfo.CollectionType,
   myClassInfo.ObjectType
);

myCollection = generic.Invoke(null, new object[] { csvData });
using System;
using System.Collections;
using System.Collections.Generic;

namespace DictionaryRuntime
{
    public class DynamicDictionaryFactory
    {
        /// <summary>
        /// Factory to create dynamically a generic Dictionary.
        /// </summary>
        public IDictionary CreateDynamicGenericInstance(Type keyType, Type valueType)
        {
            //Creating the Dictionary.
            Type typeDict = typeof(Dictionary<,>);

            //Creating KeyValue Type for Dictionary.
            Type[] typeArgs = { keyType, valueType };

            //Passing the Type and create Dictionary Type.
            Type genericType = typeDict.MakeGenericType(typeArgs);

            //Creating Instance for Dictionary<K,T>.
            IDictionary d = Activator.CreateInstance(genericType) as IDictionary;

            return d;

        }
    }
}
using System;
using System.Collections.Generic;

namespace DynamicDictionary
{
    class Test
    {
        static void Main(string[] args)
        {
            var factory = new DictionaryRuntime.DynamicDictionaryFactory();
            var dict = factory.CreateDynamicGenericInstance(typeof(String), typeof(int));

            var typedDict = dict as Dictionary<String, int>;

            if (typedDict != null)
            {
                Console.WriteLine("Dictionary<String, int>");

                typedDict.Add("One", 1);
                typedDict.Add("Two", 2);
                typedDict.Add("Three", 3);

                foreach(var kvp in typedDict)
                {
                    Console.WriteLine("\"" + kvp.Key + "\": " + kvp.Value);
                }
            }
            else
                Console.WriteLine("null");
        }
    }
}
Dictionary<String, int>
"One": 1
"Two": 2
"Three": 3
public class Bar { }
public class Square { }
public class myClass
{
    public void Foo<T>(T item)
    {
        Console.WriteLine(typeof(T).Name);
    }
}
public static class Extension
{
    public static void InvokeFoo<T>(this T t)
    {
        var fooMethod = typeof(myClass).GetMethod("Foo");
        var tType = typeof(T);
        var fooTMethod = fooMethod.MakeGenericMethod(new[] { tType });
        fooTMethod.Invoke(new myClass(), new object[] { t });
    }
}
var objSquare = new Square();
objSquare.InvokeFoo();

var objBar = new Bar();
objBar.InvokeFoo();