当类型参数在编译时未知,而是在运行时动态获取时,调用泛型方法的最佳方法是什么?
请考虑以下示例代码 - 在 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(nameof(Sample.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 : IItem
Alpha
类不实现此接口(。但这就是重点。编译器没有此调用有效的信息。作为程序员,您知道这一点,并且应该确保此代码运行没有错误。
返回类型陷阱
当您使用动态类型的参数调用非 void 方法时,其返回类型也可能是dynamic
。因此,如果您将前面的示例更改为此代码:
var result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
则结果对象的类型将为 dynamic
。这是因为编译器并不总是知道将调用哪个方法。如果您知道函数调用的返回类型,则应将其隐式转换为所需的类型,以便其余代码是静态类型的:
string result = ProcessItem((dynamic)testObjects[i], "test" + i, i);
如果类型不匹配,则会收到运行时错误。
实际上,如果您尝试获取上一示例中的结果值,那么您将在第二个循环迭代中收到运行时错误。这是因为您尝试保存 void 函数的返回值。
补充Adrian Gallero的答案:
从类型信息调用泛型方法涉及三个步骤。
##TLDR:可以使用类型对象调用已知的泛型方法,可以通过以下方式完成:##
((Action)GenericMethod<object>)
.Method
.GetGenericMethodDefinition()
.MakeGenericMethod(typeof(string))
.Invoke(this, null);
其中GenericMethod<object>
是要调用的方法名和满足泛型约束的任何类型。
动作(匹配要调用的方法的签名,即(Func<string,string,int>
或Action<bool>
(
##Step 1 是获取泛型方法定义的 MethodInfo##
###Method 1:将 GetMethod(( 或 GetMethods(( 与适当的类型或绑定标志一起使用.###
MethodInfo method = typeof(Sample).GetMethod("GenericMethod");
###Method 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();
在 C# 中,方法的名称,即"ToString"或"GenericMethod"实际上是指可能包含一个或多个方法的一组方法。 在提供方法参数的类型之前,不知道哪个您所指的方法。
((Action)GenericMethod<object>)
是指特定方法的委托。 ((Func<string, int>)GenericMethod<object>)
指泛型方法的不同重载
##Method 3:创建一个包含方法调用表达式的 lambda 表达式,获取 MethodInfo 对象,然后获取 GetGenericMethodDefinition
MethodInfo method = ((MethodCallExpression)((Expression<Action<Sample>>)(
(Sample v) => v.GenericMethod<object>()
)).Body).Method.GetGenericMethodDefinition();
这分解为
创建一个 lambda 表达式,其中正文是对所需方法的调用。
Expression<Action<Sample>> expr = (Sample v) => v.GenericMethod<object>();
提取主体并强制转换为 MethodCallExpression
MethodCallExpression methodCallExpr = (MethodCallExpression)expr.Body;
从方法中获取泛型方法定义
MethodInfo methodA = methodCallExpr.Method.GetGenericMethodDefinition();
##Step 2 正在调用 MakeGenericMethod 来创建具有适当类型的泛型方法。
MethodInfo generic = method.MakeGenericMethod(myType);
##Step 3 是使用适当的参数调用方法。##
generic.Invoke(this, null);
在 C# 4.0 中,不需要反射,因为 DLR 可以使用运行时类型调用它。由于使用 DLR 库在动态上是一种痛苦(而不是 C# 编译器为您生成代码(,因此开源框架 Dynamitey (.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}));
没有人提供">经典反射"解决方案,所以这里有一个完整的代码示例:
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;
}
}
}
上面的DynamicDictionaryFactory
类有一个方法
CreateDynamicGenericInstance(Type keyType, Type valueType)
它创建并返回一个 IDictionary 实例,其键和值的类型与调用keyType
和valueType
上指定的类型完全相同。
下面是如何调用此方法以实例化和使用Dictionary<String, int>
的完整示例:
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
<</div>
div class="one_answers"> 这是我基于 Grax 答案的 2 美分,但通用方法需要两个参数。
假设您的方法在帮助程序类中定义如下:
public class Helpers
{
public static U ConvertCsvDataToCollection<U, T>(string csvData)
where U : ObservableCollection<T>
{
//transform code here
}
}
在我的例子中,U 类型始终是存储类型 T 对象的可观察集合。
由于我预定义了我的类型,因此我首先创建表示可观察集合 (U( 和存储在其中的对象 (T( 的"虚拟"对象,并在下面用于在调用 Make 时获取它们的类型
object myCollection = Activator.CreateInstance(collectionType);
object myoObject = Activator.CreateInstance(objectType);
然后调用 GetMethod 来查找泛型函数:
MethodInfo method = typeof(Helpers).
GetMethod("ConvertCsvDataToCollection");
到目前为止,上面的调用与上面解释的内容几乎相同,但是当您需要向其传递多个参数时,差异很小。
你需要将一个 Type[] 数组传递给 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。
受到谜题答案的启发 - 假设您有两个(或更多(类,例如
public class Bar { }
public class Square { }
并且你想用 Bar
和 Square
调用方法Foo<T>
,它声明为
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 });
}
}
有了这个,您可以简单地调用Foo
,例如:
var objSquare = new Square();
objSquare.InvokeFoo();
var objBar = new Bar();
objBar.InvokeFoo();
它适用于每个班级。在这种情况下,它将输出:
广场
酒吧
尽管这是一个相当古老的问题,但我发现它很有趣,因为有几个选项可以动态调用方法。从字面上看,它是反射、表达式树和发射器。从历史上看,反射是最慢的选择,而发射器是最快的选择。因此,我决定在这个有趣的案例中比较它们,看看现在是否有任何变化。原始问题要求 ** 在编译时不知道类型参数时调用泛型方法的最佳方式**。然而,上面几乎所有的答案都建议使用反射。
我为所有提到的方法创建了三个测试用例。首先,这里有一个稍微修改的示例类,将使用 3 种方法进行测试:TestReflection、TestExpression 和 TestEmit。
public class Sample
{
public void TestDirectCall(Type type)
{
GenericMethod<string>();
GenericMethodWithArg<string>(42);
StaticMethod<string>();
StaticMethodWithArg<string>(6);
}
public void TestReflection(Type type)
{
CallViaReflection.CallGenericMethod(this, type);
CallViaReflection.CallGenericMethod(this, type, 42);
CallViaReflection.CallStaticMethod(type);
CallViaReflection.CallStaticMethod(type, 6);
}
public void TestExpression(Type type)
{
CallViaExpression.CallGenericMethod(this, type);
CallViaExpression.CallGenericMethod(this, type, 42);
CallViaExpression.CallStaticMethod(type);
CallViaExpression.CallStaticMethod(type, 6);
}
public void TestEmit(Type type)
{
CallViaEmit.CallGenericMethod(this, type);
CallViaEmit.CallGenericMethod(this, type, 42);
CallViaEmit.CallStaticMethod(type);
CallViaEmit.CallStaticMethod(type, 6);
}
public void T()
{
StaticMethod<string>();
}
public void GenericMethod<T>()
{
}
public void GenericMethodWithArg<T>(int someValue)
{
}
public static void StaticMethod<T>()
{
}
public static void StaticMethodWithArg<T>(int someValue)
{
}
}
类 CallViaReflection 表示一个帮助程序类,该类通过反射调用泛型方法。我决定引入缓存以获得更好的结果。
public static class CallViaReflection
{
private readonly static Cache<MethodInfo> cache = new();
public static void CallGenericMethod(Sample sample, Type genericType)
{
var callDelegate = GetDelegate(nameof(Sample.GenericMethod), BindingFlags.Instance | BindingFlags.Public, genericType);
callDelegate.Invoke(sample, null);
}
public static void CallGenericMethod(Sample sample, Type genericType, int someValue)
{
var callDelegate = GetDelegate(nameof(Sample.GenericMethodWithArg), BindingFlags.Instance | BindingFlags.Public, genericType, typeof(int));
callDelegate.Invoke(sample, new object[] { someValue });
}
public static void CallStaticMethod(Type genericType)
{
var callDelegate = GetDelegate(nameof(Sample.StaticMethod), BindingFlags.Static | BindingFlags.Public, genericType);
callDelegate.Invoke(null, null);
}
public static void CallStaticMethod(Type genericType, int someValue)
{
var callDelegate = GetDelegate(nameof(Sample.StaticMethodWithArg), BindingFlags.Static | BindingFlags.Public, genericType, typeof(int));
callDelegate.Invoke(null, new object[] { someValue });
}
private static MethodInfo GetDelegate(string methodName, BindingFlags bindingFlags, Type genericType, params Type[] arguments)
{
if (cache.TryGet(methodName, genericType, out var concreteMethodInfo))
return concreteMethodInfo;
var sampleType = typeof(Sample);
MethodInfo genericMethodInfo = sampleType.GetMethod(methodName, bindingFlags)!;
concreteMethodInfo = genericMethodInfo.MakeGenericMethod(genericType);
cache.Add(methodName, genericType, concreteMethodInfo);
return concreteMethodInfo;
}
}
下一个类 CallViaExpression 使用缓存的表达式树。
public static class CallViaExpression
{
private static readonly Cache<Delegate> cache = new();
public static void CallGenericMethod(Sample sample, Type genericType)
{
var callDelegate = GetDelegate(nameof(Sample.GenericMethod), BindingFlags.Instance | BindingFlags.Public, genericType);
((Action<Sample>)callDelegate).Invoke(sample);
}
public static void CallGenericMethod(Sample sample, Type genericType, int someValue)
{
var callDelegate = GetDelegate(nameof(Sample.GenericMethodWithArg), BindingFlags.Instance | BindingFlags.Public, genericType, typeof(int));
((Action<Sample, int>)callDelegate).Invoke(sample, someValue);
}
public static void CallStaticMethod(Type genericType)
{
var callDelegate = GetDelegate(nameof(Sample.StaticMethod), BindingFlags.Static | BindingFlags.Public, genericType);
((Action)callDelegate).Invoke();
}
public static void CallStaticMethod(Type genericType, int someValue)
{
var callDelegate = GetDelegate(nameof(Sample.StaticMethodWithArg), BindingFlags.Static | BindingFlags.Public, genericType, typeof(int));
((Action<int>)callDelegate).Invoke(someValue);
}
private static Delegate GetDelegate(string methodName, BindingFlags bindingFlags, Type genericType, params Type[] arguments)
{
if (cache.TryGet(methodName, genericType, out var callDelegate))
return callDelegate;
var sampleType = typeof(Sample);
MethodInfo genericMethodInfo = sampleType.GetMethod(methodName, bindingFlags)!;
var concreteMethodInfo = genericMethodInfo.MakeGenericMethod(genericType);
var argumentExpr = arguments.Select((type, i) => Expression.Parameter(type, "arg" + i)).ToArray();
if (concreteMethodInfo.IsStatic)
{
var callExpr = Expression.Call(concreteMethodInfo, argumentExpr);
callDelegate = Expression.Lambda(callExpr, argumentExpr).Compile();
}
else
{
var parameterExpr = Expression.Parameter(sampleType, "sample");
var callExpr = Expression.Call(parameterExpr, concreteMethodInfo, argumentExpr);
callDelegate = Expression.Lambda(callExpr, new[] { parameterExpr }.Union(argumentExpr).ToArray()).Compile();
}
cache.Add(methodName, genericType, callDelegate);
return callDelegate;
}
}
最后但并非最不重要的一点是,CallViaEmit发出必要的操作。
public static class CallViaEmit
{
private static readonly Cache<Delegate> cache = new();
public static void CallGenericMethod(this Sample sample, Type genericType)
{
var callDelegate = GetDynamicMethod(nameof(Sample.GenericMethod), BindingFlags.Instance | BindingFlags.Public, genericType);
((Action<Sample>)callDelegate).Invoke(sample);
}
public static void CallGenericMethod(this Sample sample, Type genericType, int someValue)
{
var callDelegate = GetDynamicMethod(nameof(Sample.GenericMethodWithArg), BindingFlags.Instance | BindingFlags.Public, genericType);
((Action<Sample, int>)callDelegate).Invoke(sample, someValue);
}
public static void CallStaticMethod(Type genericType)
{
var callDelegate = GetDynamicMethod(nameof(Sample.StaticMethod), BindingFlags.Static | BindingFlags.Public, genericType);
((Action)callDelegate).Invoke();
}
public static void CallStaticMethod(Type genericType, int someValue)
{
var callDelegate = GetDynamicMethod(nameof(Sample.StaticMethodWithArg), BindingFlags.Static | BindingFlags.Public, genericType);
((Action<int>)callDelegate).Invoke(someValue);
}
private static Delegate GetDynamicMethod(string methodName, BindingFlags bindingFlags, Type genericType)
{
if (cache.TryGet(methodName, genericType, out var callDelegate))
return callDelegate;
var genericMethodInfo = typeof(Sample).GetMethod(methodName, bindingFlags)!;
var concreteMethodInfo = genericMethodInfo.MakeGenericMethod(genericType);
var argumentTypes = concreteMethodInfo.GetParameters().Select(x => x.ParameterType).ToArray(); ;
var dynamicMethodArgs = concreteMethodInfo.IsStatic
? argumentTypes
: new[] { typeof(Sample) }.Union(argumentTypes).ToArray();
var dynamicMethod = new DynamicMethod("DynamicCall", null, dynamicMethodArgs);
var il = dynamicMethod.GetILGenerator();
il.Emit(OpCodes.Nop);
switch (dynamicMethodArgs.Length)
{
case 0:
break;
case 1:
il.Emit(OpCodes.Ldarg_0);
break;
case 2:
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
break;
case 3:
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
break;
default:
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldarg_2);
il.Emit(OpCodes.Ldarg_3);
for (int i = 4; i < argumentTypes.Length; i++)
{
il.Emit(OpCodes.Ldarg, argumentTypes[i]);
}
break;
}
il.EmitCall(concreteMethodInfo.IsStatic ? OpCodes.Call : OpCodes.Callvirt, concreteMethodInfo, null);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ret);
callDelegate = dynamicMethod.CreateDelegate(GetActionType(dynamicMethodArgs));
cache.Add(methodName, genericType, callDelegate);
return callDelegate;
}
private static Type GetActionType(Type[] argumentTypes)
{
switch (argumentTypes.Length)
{
case 0:
return typeof(Action);
case 1:
return typeof(Action<>).MakeGenericType(argumentTypes);
case 2:
return typeof(Action<,>).MakeGenericType(argumentTypes);
case 3:
return typeof(Action<,,>).MakeGenericType(argumentTypes);
case 4:
return typeof(Action<,,,>).MakeGenericType(argumentTypes);
case 5:
return typeof(Action<,,,,>).MakeGenericType(argumentTypes);
case 6:
return typeof(Action<,,,,,>).MakeGenericType(argumentTypes);
case 7:
return typeof(Action<,,,,,,>).MakeGenericType(argumentTypes);
case 8:
return typeof(Action<,,,,,,,>).MakeGenericType(argumentTypes);
default:
throw new NotSupportedException("Action with more than 8 arguments is not supported");
}
}
}
最后,这是一个测试类和测试结果。
[TestFixture]
public class SampleTests
{
private const int Iterations = 10000000;
[Test]
public void TestDirectCall()
{
var sample = new Sample();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < Iterations; i++)
sample.TestDirectCall(typeof(string));
stopwatch.Stop();
Assert.Pass($"Calling methods directly took {stopwatch.ElapsedMilliseconds} milliseconds.");
}
[Test]
public void TestReflection()
{
var sample = new Sample();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < Iterations; i++)
sample.TestReflection(typeof(string));
stopwatch.Stop();
Assert.Pass($"Calling methods dynamically via reflection took {stopwatch.ElapsedMilliseconds} milliseconds.");
}
[Test]
public void TestExpressionTree()
{
var sample = new Sample();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < Iterations; i++)
sample.TestExpression(typeof(string));
stopwatch.Stop();
Assert.Pass($"Calling methods dynamically via expression tree took {stopwatch.ElapsedMilliseconds} milliseconds.");
}
[Test]
public void TestEmit()
{
var sample = new Sample();
var stopwatch = new Stopwatch();
stopwatch.Start();
for (var i = 0; i < Iterations; i++)
sample.TestEmit(typeof(string));
stopwatch.Stop();
Assert.Pass($"Calling methods dynamically via emit took {stopwatch.ElapsedMilliseconds} milliseconds.");
}
}
通过 emit 动态调用方法需要 2939 毫秒。通过表达式树动态调用方法需要 3910 毫秒。通过反射动态调用方法需要 6381 毫秒。
显然,赢家是发射器。它的执行速度仍然快两倍以上。第二位是表达式树。
因此,我的判决在第二个十年里还没有改变。如果需要动态调用方法,请开始使用表达式树。如果代码对性能至关重要,请使用 ILGenerator 并发出必要的调用。尽管乍一看可能看起来很复杂,但所有必要的MSIL指令都可以使用ildasm轻松拆卸。
源代码可在 GitHub 上找到。