static LambdaExpression ApplyMethodHandler(string functionName, LambdaExpression functionLambda, IFunctionExecutionHandler handler)
{
// public static int MyMethod(object arg0, int arg1) { ... }
// becomes:
// (the 'handler' object is captured and called mh)
// public static int MyMethodWrapped(object arg0, int arg1)
// {
// var fhArgs = new FunctionExecutionArgs("MyMethod", new object[] { arg0, arg1});
// int result = default(int);
// try
// {
// fh.OnEntry(fhArgs);
// if (fhArgs.FlowBehavior == FlowBehavior.Return)
// {
// result = (int)fhArgs.ReturnValue;
// }
// else
// {
// // Inner call
// result = MyMethod(arg0, arg1);
// fhArgs.ReturnValue = result;
// fh.OnSuccess(fhArgs);
// result = (int)fhArgs.ReturnValue;
// }
// }
// catch ( Exception ex )
// {
// fhArgs.Exception = ex;
// fh.OnException(fhArgs);
// // Makes no sense to me yet - I've removed this FlowBehavior enum value.
// // if (fhArgs.FlowBehavior == FlowBehavior.Continue)
// // {
// // // Finally will run, but can't change return value
// // // Should we assign result...?
// // // So Default value will be returned....?????
// // fhArgs.Exception = null;
// // }
// // else
// if (fhArgs.FlowBehavior == FlowBehavior.Return)
// {
// // Clear the Exception and return the ReturnValue instead
// // Finally will run, but can't further change return value
// fhArgs.Exception = null;
// result = (int)fhArgs.ReturnValue;
// }
// else if (fhArgs.FlowBehavior == FlowBehavior.ThrowException)
// {
// throw fhArgs.Exception;
// }
// else // if (fhArgs.FlowBehavior == FlowBehavior.Default || fhArgs.FlowBehavior == FlowBehavior.RethrowException)
// {
// throw;
// }
// }
// finally
// {
// fh.OnExit(fhArgs);
// // NOTE: fhArgs.ReturnValue is not used again here...!
// }
//
// return result;
// }
// }
// CONSIDER: There are some helpers in .NET to capture the exception context, which would allow us to preserve the stack trace in a fresh throw.
// Ensure the handler object is captured.
var mh = Expression.Constant(handler);
var funcName = Expression.Constant(functionName);
// Prepare the functionHandlerArgs that will be threaded through the handler,
// and a bunch of expressions that access various properties on it.
var fhArgs = Expr.Variable(typeof(FunctionExecutionArgs), "fhArgs");
var fhArgsReturnValue = SymbolExtensions.GetProperty(fhArgs, (FunctionExecutionArgs mea) => mea.ReturnValue);
var fhArgsException = SymbolExtensions.GetProperty(fhArgs, (FunctionExecutionArgs mea) => mea.Exception);
var fhArgsFlowBehaviour = SymbolExtensions.GetProperty(fhArgs, (FunctionExecutionArgs mea) => mea.FlowBehavior);
// Set up expressions to call the various handler methods.
// TODO: Later we can determine which of these are actually implemented, and only write out the code needed in the particular case.
var onEntry = Expr.Call(mh, SymbolExtensions.GetMethodInfo <IFunctionExecutionHandler>(meh => meh.OnEntry(null)), fhArgs);
var onSuccess = Expr.Call(mh, SymbolExtensions.GetMethodInfo <IFunctionExecutionHandler>(meh => meh.OnSuccess(null)), fhArgs);
var onException = Expr.Call(mh, SymbolExtensions.GetMethodInfo <IFunctionExecutionHandler>(meh => meh.OnException(null)), fhArgs);
var onExit = Expr.Call(mh, SymbolExtensions.GetMethodInfo <IFunctionExecutionHandler>(meh => meh.OnExit(null)), fhArgs);
// Create the new parameters for the wrapper
var outerParams = functionLambda.Parameters.Select(p => Expr.Parameter(p.Type, p.Name)).ToArray();
// Create the array of parameter values that will be put into the method handler args.
var paramsArray = Expr.NewArrayInit(typeof(object), outerParams.Select(p => Expr.Convert(p, typeof(object))));
// Prepare the result and ex(ception) local variables
var result = Expr.Variable(functionLambda.ReturnType, "result");
var ex = Expression.Parameter(typeof(Exception), "ex");
// A bunch of helper expressions:
// : new FunctionExecutionArgs(new object[] { arg0, arg1 })
var fhArgsConstr = typeof(FunctionExecutionArgs).GetConstructor(new[] { typeof(string), typeof(object[]) });
var newfhArgs = Expr.New(fhArgsConstr, funcName, paramsArray);
// : result = (int)fhArgs.ReturnValue
var resultFromReturnValue = Expr.Assign(result, Expr.Convert(fhArgsReturnValue, functionLambda.ReturnType));
// : fhArgs.ReturnValue = (object)result
var returnValueFromResult = Expr.Assign(fhArgsReturnValue, Expr.Convert(result, typeof(object)));
// : result = function(arg0, arg1)
var resultFromInnerCall = Expr.Assign(result, Expr.Invoke(functionLambda, outerParams));
// Build the Lambda wrapper, with the original parameters
var lambda = Expr.Lambda(
Expr.Block(new[] { fhArgs, result },
Expr.Assign(fhArgs, newfhArgs),
Expr.Assign(result, Expr.Default(result.Type)),
Expr.TryCatchFinally(
Expr.Block(
onEntry,
Expr.IfThenElse(
Expr.Equal(fhArgsFlowBehaviour, Expr.Constant(FlowBehavior.Return)),
resultFromReturnValue,
Expr.Block(
resultFromInnerCall,
returnValueFromResult,
onSuccess,
resultFromReturnValue))),
onExit, // finally
Expr.Catch(ex,
Expr.Block(
Expr.Assign(fhArgsException, ex),
onException,
Expr.IfThenElse(
Expr.Equal(fhArgsFlowBehaviour, Expr.Constant(FlowBehavior.Return)),
Expr.Block(
Expr.Assign(fhArgsException, Expr.Constant(null, typeof(Exception))),
resultFromReturnValue),
Expr.IfThenElse(
Expr.Equal(fhArgsFlowBehaviour, Expr.Constant(FlowBehavior.ThrowException)),
Expr.Throw(fhArgsException),
Expr.Rethrow()))))
),
result),
functionName,
outerParams);
return(lambda);
}