using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Collections.Generic;
namespace TheFunctionFactory
{
///
/// Creates and compiles a Func using expressions
///
///
/// Inspired from:
/// http://abhi.dcmembers.com/blog/2009/03/25/lambda-based-reflection-vs-normal-reflection-vs-direct-call-4/
///
public static class FunctionFactory
{
///
/// Creates a compiled delegate function for the specified type and method name
///
/// Delegate Func to create
/// Constant to get method from
/// Method to examine
/// Delegate function of the specified methodname
public static TFunc CreateFunc(object obj, string methodName)
{
List args = new List();
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(methodinvokeExpression, args.ToArray());
//now the following Lambda is created:
// (TArg1, TArg2) => obj.MethodName(TArg1, TArg2);
return lambda.Compile();
}
return default(TFunc);
}
///
/// Creates a compiled delegate function using expressions,
/// the first Func{TObject,TReturn} parameter must be the constant to be passed in
///
/// Delegate Func to create
/// Type of constant to pass in to the Func
/// Method to examine
/// Delegate function of the specified methodname
///
/// The function Func{TType,TArg1,TArg2} with a method name of "CallMe" would create the following
/// lambda:
///
/// (TType, TArg1, TArg2) => TType.CallMe(TArg1, TArg2);
///
///
public static TFunc CreateFunc(Type targetType, string methodName)
{
List args = new List();
MethodInfo minfo = targetType.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public);
if (minfo != null)
{
Type objectType = typeof(TFunc).GetGenericArguments().First();
var targetParam = Expression.Parameter(objectType, "a");
if (!targetType.IsAssignableFrom(objectType))
throw new InvalidCastException(string.Format("{0} cannot be cast to {1}", targetType.Name, objectType.Name));
var target = Expression.Convert(targetParam, targetType);
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(methodinvokeExpression, new ParameterExpression[]{targetParam}.Concat(args));
//now the following Lambda is created:
// (a, TArg1, TArg2) => a.MethodName(TArg1, TArg2);
return lambda.Compile();
}
return default(TFunc);
}
}
}