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();
}
You are facing two separate problems:
Let's deal with the two separately.
Let's say that you have the following information available:
ConstructorInfo
representing the first item in the chain (the constructor)MethodInfo
objects - one for each chained functionThen 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();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With