Reputation: 6249
Although I was (and for this project still am) limited to .NET 3.5, I have had success using the DLR version of Expression Trees. Which is released under the Apache License version 2.0.
This added support for all (maybe a few more or less, but probably not) .NET 4.0+ Expressions such as BlockExpression
which I needed for this question.
The source code can be found on GitHub.
In my current project I'm compiling an Expression tree with a variable amount of parameters. I have a chain of Expressions
that need to be invoked. In .NET 4.0+ I'd just use Expression.Block
to achieve this, however, I am limited to using .NET 3.5 on this project.
Now I have found a massive hack to work around this issue, but I don't believe this is the best way to go about this.
The code:
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
class Program
{
struct Complex
{
public float Real;
public float Imaginary;
}
// Passed to all processing functions
class ProcessContext
{
public ConsoleColor CurrentColor;
}
// Process functions. Write to console as example.
static void processString(ProcessContext ctx, string s)
{ Console.ForegroundColor = ctx.CurrentColor; Console.WriteLine("String: " + s); }
static void processAltString(ProcessContext ctx, string s)
{ Console.ForegroundColor = ctx.CurrentColor; Console.WriteLine("AltString: " + s); }
static void processInt(ProcessContext ctx, int i)
{ Console.ForegroundColor = ctx.CurrentColor; Console.WriteLine("Int32: " + i); }
static void processComplex(ProcessContext ctx, Complex c)
{ Console.ForegroundColor = ctx.CurrentColor; Console.WriteLine("Complex: " + c.Real + " + " + c.Imaginary + "i"); }
// Using delegates to access MethodInfo, just to simplify example.
static readonly MethodInfo _processString = new Action<ProcessContext, string>(processString).Method;
static readonly MethodInfo _processAltString = new Action<ProcessContext, string>(processAltString).Method;
static readonly MethodInfo _processInt = new Action<ProcessContext, int>(processInt).Method;
static readonly MethodInfo _processComplex = new Action<ProcessContext, Complex>(processComplex).Method;
static void Main(string[] args)
{
var methodNet40 = genNet40();
var methodNet35 = genNet35();
var ctx = new ProcessContext();
ctx.CurrentColor = ConsoleColor.Red;
methodNet40(ctx, "string1", "string2", 101, new Complex { Real = 5f, Imaginary = 10f });
methodNet35(ctx, "string1", "string2", 101, new Complex { Real = 5f, Imaginary = 10f });
// Both work and print in red:
// String: string1
// AltString: string2
// Int32: 101
// Complex: 5 + 10i
}
static void commonSetup(out ParameterExpression pCtx, out ParameterExpression[] parameters, out Expression[] processMethods)
{
pCtx = Expression.Parameter(typeof(ProcessContext), "pCtx");
// Hard-coded for simplicity. In the actual code these are reflected.
parameters = new ParameterExpression[]
{
// Two strings, just to indicate that the process method
// can be different between the same types.
Expression.Parameter(typeof(string), "pString"),
Expression.Parameter(typeof(string), "pAltString"),
Expression.Parameter(typeof(int), "pInt32"),
Expression.Parameter(typeof(Complex), "pComplex")
};
// Again hard-coded. In the actual code these are also reflected.
processMethods = new Expression[]
{
Expression.Call(_processString, pCtx, parameters[0]),
Expression.Call(_processAltString, pCtx, parameters[1]),
Expression.Call(_processInt, pCtx, parameters[2]),
Expression.Call(_processComplex, pCtx, parameters[3]),
};
}
static Action<ProcessContext, string, string, int, Complex> genNet40()
{
ParameterExpression pCtx;
ParameterExpression[] parameters;
Expression[] processMethods;
commonSetup(out pCtx, out parameters, out processMethods);
// What I'd do in .NET 4.0+
var lambdaParams = new ParameterExpression[parameters.Length + 1]; // Add ctx
lambdaParams[0] = pCtx;
Array.Copy(parameters, 0, lambdaParams, 1, parameters.Length);
var method = Expression.Lambda<Action<ProcessContext, string, string, int, Complex>>(
Expression.Block(processMethods),
lambdaParams).Compile();
return method;
}
static Action<ProcessContext, string, string, int, Complex> genNet35()
{
ParameterExpression pCtx;
ParameterExpression[] parameters;
Expression[] processMethods;
commonSetup(out pCtx, out parameters, out processMethods);
// Due to the lack of the Block expression, the only way I found to execute
// a method and pass the Expressions as its parameters. The problem however is
// that the processing methods return void, it can therefore not be passed as
// a parameter to an object.
// The only functional way I found, by generating a method for each call,
// then passing that as an argument to a generic Action<T> invoker with
// parameter T that returns null. A super dirty probably inefficient hack.
// Get reference to the invoke helper
MethodInfo invokeHelper =
typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic)
.Single(x => x.Name == "invokeHelper" && x.IsGenericMethodDefinition);
// Route each processMethod through invokeHelper<T>
for (int i = 0; i < processMethods.Length; i++)
{
// Get some references
ParameterExpression param = parameters[i];
Expression process = processMethods[i];
// Compile the old process to Action<T>
Type delegateType = typeof(Action<,>).MakeGenericType(pCtx.Type, param.Type);
Delegate compiledProcess = Expression.Lambda(delegateType, process, pCtx, param).Compile();
// Create a new expression that routes the Action<T> through invokeHelper<T>
processMethods[i] = Expression.Call(
invokeHelper.MakeGenericMethod(param.Type),
Expression.Constant(compiledProcess, delegateType),
pCtx, param);
}
// Now processMethods execute and then return null, so we can use it as parameter
// for any function. Get the MethodInfo through a delegate.
MethodInfo call2Helper = new Func<object, object, object>(Program.call2Helper).Method;
// Start with the last call
Expression lambdaBody = Expression.Call(call2Helper,
processMethods[processMethods.Length - 1],
Expression.Constant(null, typeof(object)));
// Then add all the previous calls
for (int i = processMethods.Length - 2; i >= 0; i--)
{
lambdaBody = Expression.Call(call2Helper,
processMethods[i],
lambdaBody);
}
var lambdaParams = new ParameterExpression[parameters.Length + 1]; // Add ctx
lambdaParams[0] = pCtx;
Array.Copy(parameters, 0, lambdaParams, 1, parameters.Length);
var method = Expression.Lambda<Action<ProcessContext, string, string, int, Complex>>(
lambdaBody,
lambdaParams).Compile();
return method;
}
static object invokeHelper<T>(Action<ProcessContext, T> method, ProcessContext ctx, T parameter)
{
method(ctx, parameter);
return null;
}
static object call2Helper(object p1, object p2) { return null; }
}
The main reason I want to find a good alternative is to not have to put this ugly hack in our code base (though I will if there's no decent alternative).
On the side though it is also quite wasteful, and it runs on a presumably weak client machine, potentially a couple of thousand times per second in a video game. Now it's not going to break or make our game's performance, but it's not neglectable. The amount of function calls for each approach.
Testing performance (in a release build) yields a 3.6 times difference in the invocation process. In debug build it's about 6 times speed difference, but that doesn't matter too much, us developers have beefier machines.
Upvotes: 3
Views: 1015
Reputation: 203842
You can Simpy the code a lot even if you keep the same (or a similar) fundamental strategy but refactor the code a bit.
Write your own Block
implementation that takes in a sequence of expressions and create a single expression that represents invoking all of them.
To do this you'll have a private implementation method that takes a number of actions and invokes all of them, you'll translate all of the expressions that you have into actions that you pass to the method, and then you can return the expression representing that method call:
//TODO come up with a better name
public class Foo
{
private static void InvokeAll(Action[] actions)
{
foreach (var action in actions)
action();
}
public static Expression Block(IEnumerable<Expression> expressions)
{
var invokeMethod = typeof(Foo).GetMethod("InvokeAll",
BindingFlags.Static | BindingFlags.NonPublic);
var actions = expressions.Select(e => Expression.Lambda<Action>(e))
.ToArray();
var arrayOfActions = Expression.NewArrayInit(typeof(Action), actions);
return Expression.Call(invokeMethod, arrayOfActions);
}
}
This doesn't involve compiling any expressions ahead of time, but more importantly it allows you to separate out the logic of creating a block of expressions from your usage, allowing you to easily bring it in/out based on what version of the framework you're using.
Upvotes: 5
Reputation: 171178
I see no reason to compile one lambda for each action you want to call. Make the processing function return something other than void
. Then you can generate:
static object Dummy(object o1, object o2, object o3, ...) { return null; }
Dummy(func1(), func2(), func3());
Dummy
should then be inlined.
Even if you have to compile multiple lambdas you don't need to accept delegate calls at runtime. You can access Delegate.Method
of the compiled lambdas and emit a static call to that method directly.
Upvotes: 0