Reputation: 363
I have a delegate that I have created in runtime because I don't know its parameter types in design time. The code is the following:
var delegMethodType = typeof(Func<,,,>).MakeGenericType(type1, type2, type3, returnType);
As you can see I'm using Func<,,,> to represent Func<T1, T2, T3, TReturn>. It works perfectly.
After that, I create my delegate in this way:
var deleg = Delegate.CreateDelegate(delegMethodType, obj, methodInfo);
Where "delegMethodType" is the type for the delegate that I created before, "obj" is the object that receives the call, and "methodInfo" is the reflection method for "obj". This works perfectly.
Now, I need to call the delegate but due to "Delegate.CreateDelegate" returns a basic delegate, I cannot call it with the parameters:
// 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);
I can use the delegate with "deleg.DynamicInvoke()" but it's slow than calling the delegate with its parameters.
How I can call this delegate with three parameters and get its return value?
Upvotes: 0
Views: 672
Reputation: 2008
It's possible to just invoke the methodInfo directly, or somewhat more complicatedly use the Linq Expressions library to create a Func<object,object,...>
that can be invoked that does all of the required casts.
Note that I am assuming that you do know the number of parameters at compile time, as your question indicates with the Func<,,,>
usage.
It's a bit involved, but basically comes down to using Expression.Lambda(...).Compile()
to create the final func that will be invoked, Expression.Convert()
to cast from objects to the actual types so that Expression.Call()
can be used to call the method on objects with the correct types from our object Expression.Parameter
s
This example shows how to implement both approaches.
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);
}
}
}
If you want to call the same function for multiple objects, then you can also make the subject a Func
parameter too - just make it a separate Expression.Parameter(typeof(object))
at the start, include that parameter instead of the Expression.Constant
and also include in the list of parameters for Expression.Lambda
. Then also pass in the subject as a parameter when invoking.
Upvotes: 1