具有运行时泛型类型的 c# 调用委托



我有一个在运行时创建的委托,因为我不知道它在设计时的参数类型。代码如下:

var delegMethodType = typeof(Func<,,,>).MakeGenericType(type1, type2, type3, returnType);

如您所见,我正在使用 Func<,,,> 来表示 Func<T1、T2、T3、TReturn>。它工作得很好。

之后,我以这种方式创建我的委托:

var deleg = Delegate.CreateDelegate(delegMethodType, obj, methodInfo);

其中"delegMethodType"是我之前创建的委托的类型,"obj"是接收调用的对象,"methodInfo"是"obj"的反射方法。这非常有效。

现在,我需要调用委托,但由于"Delegate.CreateDelegate"返回一个基本委托,我无法使用参数调用它:

// due to this delegate was created in runtime VS.NET doesn't recognize
// that the delegate allows three parameters and returns a value
var result = deleg(val1, val2, val3);

我可以将委托与"deleg.DynamicInvoke()",但它比使用其参数调用委托慢。

如何使用三个参数调用此委托并获取其返回值?

可以直接调用 methodInfo,也可以更复杂的使用 Linq 表达式库来创建可以调用的执行所有必需强制转换的Func<object,object,...>

请注意,我假设您确实知道编译时的参数数量,正如您的问题在Func<,,,>用法中所示。

这有点复杂,但基本上归结为使用Expression.Lambda(...).Compile()来创建将被调用的最终 func,Expression.Convert()从对象转换为实际类型,以便Expression.Call()可用于从我们的对象Expression.Parameter中调用具有正确类型的对象的方法

。此示例演示如何实现这两种方法。

using System;
using System.Linq.Expressions;
namespace SO
{
class SO69847110
{
public TR fn<T1,T2,T3,TR>(T1 p1, T2 p2, T3 p3)
{
if(typeof(TR) == typeof(string))
{
return (TR)(object)"Hello World";
}
return default(TR);
}
static void Main(string[] args)
{
var returnType = typeof(string);
var paramType = typeof(int);
var delegMethodType = typeof(Func<,,,>).
MakeGenericType(
paramType,
paramType,
paramType,
returnType
);
var methodInfo = typeof(SO69847110).GetMethod("fn")
.MakeGenericMethod(
paramType,
paramType,
paramType,
returnType
);
var obj = new SO69847110();
//var deleg = Delegate.CreateDelegate(delegMethodType, obj, methodInfo);
var methodInfoInvoke = methodInfo.Invoke(obj, new object[] { 0, 1, 2 });
Console.WriteLine(methodInfoInvoke);
var param1 = Expression.Parameter(typeof(object));
var param2 = Expression.Parameter(typeof(object));
var param3 = Expression.Parameter(typeof(object));
var convertedParameterExpressions =
new Expression[]
{
Expression.Convert(param1,paramType),
Expression.Convert(param2,paramType),
Expression.Convert(param3,paramType),
};
var expressed = Expression.Lambda<Func<object, object, object, object>>(
Expression.Call(
Expression.Convert(Expression.Constant(obj), obj.GetType()),
methodInfo,
convertedParameterExpressions
),
tailCall:false,
parameters: new[]
{
param1,
param2,
param3
}
).Compile();
var expressedInvoked = expressed.Invoke(obj, 0, 1, 2);
Console.WriteLine(expressedInvoked);
}
}
}

如果要为多个对象调用相同的函数,那么也可以将主题设置为Func参数 - 只需在开始时将其设置为单独的Expression.Parameter(typeof(object)),包含该参数而不是Expression.Constant,并包含在Expression.Lambda参数列表中。然后在调用时将主题作为参数传入。

最新更新