当在编译时不知道type参数时,调用泛型方法的最佳方法是什么,而是在运行时动态获取?
考虑以下示例代码 - 在 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>()
{
//...
}
}
7 回答
使用C#4.0,不需要反射,因为DLR可以使用运行时类型调用它 . 由于使用DLR库是一种动态的痛苦(而不是为您生成代码的C#编译器),开源框架Dynamitey(.net标准1.5)使您可以轻松缓存运行时访问编译器将生成的相同调用为了你 .
通过使用dynamic类型而不是反射API,可以大大简化使用仅在运行时知道的类型参数调用泛型方法 .
要使用此技术,必须从实际对象(而不仅仅是
Type
类的实例)中了解类型 . 否则,您必须创建该类型的对象或使用标准反射API solution . 您可以使用Activator.CreateInstance方法创建对象 .如果你想调用泛型方法,那么"normal"用法会推断出它的类型,那么它只是将未知类型的对象转换为
dynamic
. 这是一个例子:这是这个程序的输出:
Process
是一个通用实例方法,它写入传递参数的实际类型(通过使用GetType()
方法)和泛型参数的类型(通过使用typeof
运算符) .通过将object参数转换为
dynamic
类型,我们延迟提供类型参数直到运行时 . 当使用dynamic
参数调用Process
方法时,编译器不会使用正确的类型参数调用't care about the type of this argument. The compiler generates code that at runtime checks the real types of passed arguments (by using reflection) and choose the best method to call. Here there is only this one generic method, so it' .在此示例中,输出与您编写的内容相同:
具有动态类型的版本肯定更短且更容易编写 . 您也不必担心多次调用此函数的性能 . 由于DLR中的caching机制,具有相同类型参数的下一个调用应该更快 . 当然,您可以编写缓存调用的委托的代码,但是通过使用
dynamic
类型,您可以免费获得此行为 .如果要调用的泛型方法没有参数化类型的参数(因此无法推断其类型参数),那么您可以将循环方法的调用包装在辅助方法中,如下例所示:
提高了类型安全性
使用
dynamic
对象作为替换使用反射API的真正好处是,您只会丢失对此类特定类型的编译时检查,以免出现编译时错误 . 如果在Type.GetMethod
中将方法名称作为字符串提供,并且在MethodInfo.Invoke
中将参数作为对象数组提供,则不会发生这种情况 .下面是一个简单的示例,说明了如何在编译时(注释代码)和其他运行时捕获某些错误 . 它还显示了DLR如何尝试解析要调用的方法 .
这里我们再次通过将参数转换为
dynamic
类型来执行某些方法 . 只有第一个参数's type is postponed to runtime. You will get a compiler error if the name of the method you' re调用的验证不存在或者其他参数无效(参数数量错误或类型错误) .将
dynamic
参数传递给方法时,此调用为lately bound . 方法重载解析在运行时发生,并尝试选择最佳过载 . 因此,如果使用BarItem
类型的对象调用ProcessItem
方法,那么当传递Alpha
类型的参数时,'ll actually call the non-generic method, because it is a better match for this type. However, you' ll会收到运行时错误,因为没有可以处理此对象的方法(泛型方法具有约束where T : IItem
和Alpha
类完全没有 . 编译器没有这个调用有效的信息 . 作为程序员你知道这一点,你应该确保这个代码运行没有错误 .返回类型问题
当您使用动态类型的参数调用非void方法时,其返回类型可能是be dynamic too . 因此,如果您将以前的示例更改为此代码:
那么结果对象的类型将是
dynamic
. 这是因为编译器并不总是知道将调用哪个方法 . 如果你知道函数调用的返回类型,那么你应该implicitly convert它到所需的类型,所以其余的代码是静态类型的:如果类型不匹配,您将收到运行时错误 .
实际上,如果您尝试在前一个示例中获取结果值,那么您将在第二个循环迭代中获得运行时错误 . 这是因为您试图保存void函数的返回值 .
Nobody provided the "classic Reflection" solution, so here is a complete code example:
上面的
DynamicDictionaryFactory
类有一个方法CreateDynamicGenericInstance(Type keyType, Type valueType)
它创建并返回一个IDictionary实例,其键和值的类型完全是在调用
keyType
和valueType
上指定的 .Here is a complete example 如何调用此方法实例化并使用
Dictionary<String, int>
:执行上述控制台应用程序时,我们得到正确的预期结果:
只是对原始答案的补充 . 虽然这会奏效:
它也有点危险,因为你输了
GenericMethod
的编译时检查 . 如果您稍后进行重构并重命名GenericMethod
,则此代码将不会注意到,并且在运行时将失败 . 此外,如果对程序集进行任何后处理(例如,混淆或删除未使用的方法/类),则此代码也可能会中断 .所以,如果你知道你在编译时链接到的方法,并且这不会被调用数百万次,所以开销无关紧要,我会将此代码更改为:
虽然不是很漂亮,但你有一个编译时引用
GenericMethod
,如果你重构,删除或用_455120做任何事情,这段代码将继续工作,或者至少在编译时中断(例如你删除GenericMethod
) .执行相同操作的其他方法是创建一个新的包装类,并通过
Activator
创建它 . 我不知道是否有更好的方法 .这是基于Grax's answer的2美分,但是通用方法需要两个参数 .
假设您的方法在Helpers类中定义如下:
在我的例子中,U类型总是一个可观察的集合,存储类型为T的对象 .
由于我预先定义了我的类型,我首先创建表示可观察集合(U)和存储在其中的对象(T)的“虚拟”对象,并在调用Make时使用下面的对象来获取它们的类型
然后调用GetMethod来查找您的Generic函数:
到目前为止,上面的调用几乎与上面解释的相同,但是当你需要传递多个参数时,它们之间的差异很小 .
您需要将Type []数组传递给MakeGenericMethod函数,该函数包含上面创建的“虚拟”对象的类型:
完成后,您需要调用上面提到的Invoke方法 .
而且你已经完成了 . 有魅力!
UPDATE:
正如@Bevan强调的那样,我不需要在调用MakeGenericMethod函数时创建一个数组,因为它接受params并且我不需要创建一个对象来获取类型,因为我可以直接将类型传递给这个函数 . 在我的情况下,由于我在另一个类中预定义了类型,我只是将我的代码更改为:
myClassInfo包含2个
Type
类型的属性,我在运行时根据传递给构造函数的枚举值设置它,并将为我提供我在MakeGenericMethod中使用的相关类型 .再次感谢您突出这个@Bevan .
添加到Adrian Gallero's answer:
从类型信息调用泛型方法涉及三个步骤 .
TLDR:使用类型对象调用已知的泛型方法可以通过以下方式完成:
其中
GenericMethod<object>
是要调用的方法名称以及满足通用约束的任何类型 .(Action)匹配要调用的方法的签名,即(
Func<string,string,int>
或Action<bool>
)步骤1获取泛型方法定义的MethodInfo
方法1:将GetMethod()或GetMethods()与适当的类型或绑定标志一起使用 .
方法2:创建委托,获取MethodInfo对象,然后调用GetGenericMethodDefinition
从包含方法的类内部:
从包含方法的类的外部:
在C#中,方法的名称,即“ToString”或“GenericMethod”实际上是指可以包含一个或多个方法的一组方法 . 在提供方法参数的类型之前,不知道您指的是哪种方法 .
((Action)GenericMethod<object>)
指的是特定方法的委托 .((Func<string, int>)GenericMethod<object>)
指的是GenericMethod的不同重载方法3:创建包含方法调用表达式的lambda表达式,获取MethodInfo对象,然后获取GetGenericMethodDefinition
这打破了
创建一个lambda表达式,其中正文是对所需方法的调用 .
提取主体并强制转换为MethodCallExpression
从方法中获取泛型方法定义
步骤2调用MakeGenericMethod创建具有适当类型的泛型方法 .
步骤3使用适当的参数调用方法 .
您需要使用反射来启动方法,然后使用MakeGenericMethod提供类型参数"construct":
对于静态方法,将
null
作为Invoke
的第一个参数传递 . 那个's nothing to do with generic methods - it'只是正常的反映 .如上所述,当使用
dynamic
时,很多这样的事情比C#4更简单 - 当然,如果你可以使用类型推断 . 它没有't help in cases where type inference isn' t可用,例如问题中的确切示例 .