Dynamically create object using reflection, chained methods and lambda expressions
Asked Answered
U

2

7


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();
    }
Uraeus answered 27/10, 2012 at 21:52 Comment(0)
F
4

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();
Flatways answered 27/10, 2012 at 22:23 Comment(3)
Many thanks das.. so using your provided code - could the "makeRed" Action object be stored in "chainedArgs" and executed during the "Invoke()" call during the chaining loop?Uraeus
@Uraeus Absolutely, that's the idea. Action<MyCarType> is a delegate that can be stored in one of the arrays inside the chainedArgs array of arrays, to be passed to its corresponding method (in your case, that would be OtherProperties).Flatways
Excellent, I believe this has set me on the right path. Still wrapping my head around expression trees but I think the approach you've outlined here should do the trick. Thanks again!Uraeus
T
2

but never anything with chained methods containing lambda expressions as parameters

Well, chained methods are the bit. That's just a matter of using reflection several times. To chain together

foo.X().Y()

you need to:

  • Get method X from the declared type of foo
  • Invoke the method via reflection using the value of foo as the call target, and remember the result (e.g. tmp)
  • Get method Y from the declared type of the return type of X (see MethodInfo.ReturnType)
  • Invoke the method via reflection using the previous result (tmp) as the call target

The lambda expression is harder - it really depends on how you're going to be provided the expression in the first place. Building a delegate to execute a reasonably-arbitrary expression isn't too hard using expression trees and then calling LambdaExpression.Compile, but you need to know what you're doing.

Tamratamsky answered 27/10, 2012 at 21:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.