Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically create object using reflection, chained methods and lambda expressions


Introduction

My application instantiates an object using method chaining so it is generated and configured like so:

var car = new Car("Ferrari").Doors(2).OtherProperties(x = x.Color("Red"));


Problem

I have a requirement to dynamically generate this object at runtime - the chained methods needed for configuration will be determined at runtime so everything must be dynamically assembled on the fly. I've used reflection in the past to create simple objects like new Car("Ferrari", 2, "Red") - I'm cool with that - but never anything with chained methods containing lambda expressions as parameters - these two factors really have me stuck. I've looked into expression trees and believe this is part of the solution to create the dynamic expression parameters but am totally stuck trying to figure out how to stitch that together with reflection to create the base object and the additional chained methods.


Thanks and Appreciation

In advance for taking the time to look at my problem and for any guidance or information you might be able to provide.


UPDATE: Conclusion

Many thanks to dasblinkenlight and Jon Skeet for their answers. I picked dasblinkenlight's answer because his code sample got me off and running immediately. For the method chaining I basically used the same looping approach in the accepted answer so I won't repeat that code but below is the code I wrote to dynamically convert expression tree method calls into action delegates that could then be executed via reflection Invoke() as outlined in dasblinkenlight's answer. This, as Jon pointed out was really the crux of the problem.

Helper class to store method meta data.

public struct Argument
    {
        public string TypeName;
        public object Value;
    }

public class ExpressionTreeMethodCall
{
    public string MethodName { get; set; }
    public IList<Argument> Arguments { get; set; }

    public ExpressionTreeMethodCall()
    {
        Arguments = new List<Argument>();
    }
}


Static method to assemble a lambda expression method call and then return it as an action delegate to be executed elsewhere (passed as argument to Invoke() in my case).

public static Action<T> ConvertExpressionTreeMethodToDelegate<T>(ExpressionTreeMethodCall methodData)
    {            
        ParameterExpression type = Expression.Parameter(typeof(T));

        var arguments = new List<ConstantExpression>();
        var argumentTypes = new List<Type>();

        foreach (var a in methodData.Arguments)
        {
            arguments.Add(Expression.Constant(a.Value));
            argumentTypes.Add(Type.GetType(a.TypeName));
        }

        // Creating an expression for the method call and specifying its parameter.
        MethodCallExpression methodCall = Expression.Call(type, typeof(T).GetMethod(methodData.MethodName, argumentTypes.ToArray()), arguments);

        return Expression.Lambda<Action<T>>(methodCall, new[] { type }).Compile();
    }
like image 700
mmacneil007 Avatar asked Oct 27 '12 21:10

mmacneil007


1 Answers

You are facing two separate problems:

  • Invoking chained methods, and
  • Invoking methods that take lambdas as parameters

Let's deal with the two separately.

Let's say that you have the following information available:

  • A ConstructorInfo representing the first item in the chain (the constructor)
  • An array of objects representing constructor's parameters
  • An array of MethodInfo objects - one for each chained function
  • An array of object arrays representing parameters of each chained function

Then the process of constructing the result would look like this:

ConstructorInfo constr = ...
object[] constrArgs = ...
MethodInfo[] chainedMethods = ...
object[][] chainedArgs = ...
object res = constr.Invoke(constrArgs);
for (int i = 0 ; i != chainedMethods.Length ; i++) {
    // The chaining magic happens here:
    res = chainedMethods[i].Invoke(res, chainedArgs[i]);
}

Once the loop is over, your res contains a configured object.

The code above assumes that there are no generic methods among the chained methods; if some of the methods happen to be generic, you would need an additional step in making a callable instance of a generic method before calling Invoke.

Now let's look at the lambdas. Depending on the type of the lambda that is passed to the method, you need to pass a delegate with a particular signature. You should be able to use System.Delegate class to convert methods into callable delegates. You may need to create support methods that implement the delegates that you need. It is hard to say how without seeing the exact methods that you must be able to call through reflection. You may need to go for expression trees, and get Func<...> instances after compiling them. The call of x.Color("Red") would look like this:

Expression arg = Expression.Parameter(typeof(MyCarType));
Expression red = Expression.Constant("Red");
MethodInfo color = typeof(MyCarType).GetMethod("Color");
Expression call = Expression.Call(arg, color, new[] {red});
var lambda = Expression.Lambda(call, new[] {arg});
Action<MyCarType> makeRed = (Action<MyCarType>)lambda.Compile();
like image 95
Sergey Kalinichenko Avatar answered Oct 06 '22 01:10

Sergey Kalinichenko