Lambda Function Factory

Ever dreamed of owning a Function Factory? Unfortunately this one might not make money, but it does make functions.

The reason you might use something like this is if you are required to make a late-bound call to a specific method on an object at runtime. Of course there are various reasons and ways for doing this, the most common being via Reflection and MethodBase.Invoke().

Some projects acknowledge that fast late-bound calls are an integral part of their functionality and opt for writing DynamicMethods which invoke churning out some custom IL at runtime. Although this would probably be the most optimal way of achieving this, it is also the most complex.

A few months ago when I was writing a control for ASP.NET which was able to make decisions on how it rendered based on calling a method on the base page. The method was developer configurable via a string property that matched a certain signature (I may write about this control later, lets just say its similar to the DeviceSpecific control). I couldn't stop thinking about an article I'd found here which suggests some good benefits can be gained by using compiled Lambda expressions.

Example

So an example of using Function Factory might look like this:

var func = FunctionFactory.CreateFunc<Func<bool>>(Page, "funcName");
//Then calling the function becomes:
bool result = func();


What we have as a result is a compiled Func<> that executes the method on our desired object. The code required to build the function is not all that complicated (at least when considering IL) and can be reused by passing in different function names and Func<> definitions.

Here is CreateFunc():

/// <summary>
/// Creates a compiled delegate function for the specified type and method name
/// </summary>
/// <typeparam name="TFunc">Delegate Func to create</typeparam>
/// <param name="obj">Constant to get method from</param>
/// <param name="methodName">Method to examine</param>
/// <returns>Delegate function of the specified methodname</returns>
public static TFunc CreateFunc<TFunc>(object obj, string methodName)
{
    List<ParameterExpression> args = new List<ParameterExpression>();

    Type targetType = obj.GetType();
    MethodInfo minfo = targetType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public);

    if (minfo != null)
    {
        var target = Expression.Constant(obj);
        foreach (var arg in minfo.GetParameters())
            args.Add(Expression.Parameter(arg.ParameterType, arg.Name));
        var methodinvokeExpression = Expression.Call(target, minfo, args.ToArray());
        var lambda = Expression.Lambda<TFunc>(methodinvokeExpression, args.ToArray());

        //now the following Lambda is created:
        // (TArg1, TArg2) => obj.MethodName(TArg1, TArg2);

        return lambda.Compile();
    }
    return default(TFunc);
}


For some reason I didn't stop there either, what about the case where the function you are executing is on a type whos instance hasn't been created yet, or might change every time you need to execute the function? Well, thats not too difficult either as we can create a Func that even accepts the instance that owns the function to be executed:

var functionToCall = "Insert";
var insertFunc = FunctionFactory.CreateFunc<Func<string, int, string, string>>(typeof(string), functionToCall);
string myString = "abcdef";
var result = insertFunc(myString, 1, "zz");
//result = azzbcdef


So here "insertFunc" is a compiled function call for the "Insert" method on a given string. Of course at times, those Func<> definitions can get a little unwieldly to look at, this is where I like using the 'using' alias:

using InsertFunc = Func<string, int, string, string>;


Which allows you to specify more simply:

var insertFunc = FunctionFactory.CreateFunc<InsertFunc>(typeof(string), functionToCall);


If nothing else its been a fun little experiment, which could potentially yield a big benefit if need to do multiple late-bound calls and are not all up to date on your IL emitting.

Links

.NET Code Snippets
Posted by: Brendan Kowitz
Last revised: 21 Sep 2013 12:13PM

Comments

No comments yet. Be the first!

No new comments are allowed on this post.