Search code examples
c#autofacexpression-treeshangfire

Create Expression<Action> from Method name with overloaded constructor


I am using Hangfire and have a method to schedule jobs by assembly,type and method name. Using the default constructor works properly but all the methods have overloaded constructors that will be activated using Autofac.

//Works for a default constructor
Type type = Type.GetType("typestring, assemblystring");
var method = type.GetMethod("methodstring");
Expression[] args = new Expression[] { Expression.Constant(options,typeof(Options)) }; //All methods use the same parameters
var action = Expression.Lambda<Action>(Expression.Call(Expression.New(type), method, args));
RecurringJob.AddOrUpdate(action, "cronstring");

Trying to modify to support overloaded constructors (No default) I have this code.

Type type = Type.GetType("typestring, assemblystring");
var method = type.GetMethod("methodstring");
Expression[] args = new Expression[] { Expression.Constant(options,typeof(Options)) }; //All methods use the same parameters
var ctor = type.GetConstructors().ToList().FirstOrDefault();
var ctorParams = ctor.GetParameters();
var ctorArgs = new Expression[ctorParams.Length];
for (int i = 0; i != ctorParams.Length; ++i)
{
   ParameterExpression param = Expression.Parameter(typeof(object), ctorParams[i].Name);
   ctorArgs[i] = Expression.Convert(param, ctorParams[i].ParameterType);
}            
var ctorExpress = Expression.New(ctor, ctorArgs);
var action = Expression.Lambda<Action>(Expression.Call(ctorExpress, method, args));
RecurringJob.AddOrUpdate(action, "cronstring"); 

I receive this error: InvalidOperationException: variable '{first constructor param}' of type 'System.Object' referenced from scope '', but it is not defined

I am not sure if I am missing something or am going the wrong way about this. I have limited experience using expressions.


Solution

  • If I'm not mistaken and there is another code that would compile your expression and calls it, providing as a parameters object[], and you have to put those parameters into constructor sequentially, then you can use sample code below:

    static void Main(string[] args)
    {
        Type type = typeof(Foo);
        var ctor = type.GetConstructor(new[] { typeof(int), typeof(string) });
        var ctorParams = ctor.GetParameters();
        var dynPar = Expression.Parameter(typeof(object[]), "d");
        var ctorArgs = new Expression[ctorParams.Length];
        for (int i = 0; i != ctorParams.Length; ++i)
        {
            var indVal = Expression.ArrayIndex(dynPar, Expression.Constant(i));
            ctorArgs[i] = Expression.Convert(indVal, ctorParams[i].ParameterType);
        }
        //{ new Foo(Convert(d[0], Int32), Convert(d[1], String))}
        var ctorExpress = Expression.New(ctor, ctorArgs);
        //{ new Foo(Convert(d[0], Int32), Convert(d[1], String)).Run()}
        var callRun = Expression.Call(ctorExpress, type.GetMethod("Run"));
        //{ d => new Foo(Convert(d[0], Int32), Convert(d[1], String)).Run()}
        var action = Expression.Lambda<Action<object[]>>(callRun, dynPar);
        action.Compile()(new object[] { 1, "a" });
    }
    
    public class Foo
    {
        public Foo(int a, string b)
        {
            A = a;
            B = b;
        }
        public int A { get; }
        public string B { get; }
        public void Run()
        {
            Console.WriteLine($"It is {A} and {B}");
        }
    }