Expression Trees: Bind1st, Bind2nd
Expression Trees に対する Bind1st, Bind2nd.InvocationExpression のおかげで書くだけなら実はそんなに難しくないですな.
public static partial class ExprUtil { public static Expression<Action> Bind1st<T>(this Expression<Action<T>> expr, T arg1) { return Expression.Lambda<Action>( Expression.Invoke( expr, Expression.Constant(arg1, typeof(T)) )); } public static Expression<Action<T2>> Bind1st<T1, T2>(this Expression<Action<T1, T2>> expr, T1 arg1) { var arg2 = Expression.Parameter(typeof(T2), "arg2"); return Expression.Lambda<Action<T2>>( Expression.Invoke( expr, Expression.Constant(arg1, typeof(T1)), arg2 ), arg2); } public static Expression<Action<T1>> Bind2nd<T1, T2>(this Expression<Action<T1, T2>> expr, T2 arg2) { var arg1 = Expression.Parameter(typeof(T1), "arg1"); return Expression.Lambda<Action<T1>>( Expression.Invoke( expr, arg1, Expression.Constant(arg2, typeof(T2)) ), arg1); } public static Expression<Func<TResult>> Bind1st<T, TResult>(this Expression<Func<T, TResult>> expr, T arg1) { return Expression.Lambda<Func<TResult>>( Expression.Invoke( expr, Expression.Constant(arg1, typeof(T)) )); } public static Expression<Func<T2, TResult>> Bind1st<T1, T2, TResult>(this Expression<Func<T1, T2, TResult>> expr, T1 arg1) { var arg2 = Expression.Parameter(typeof(T2), "arg2"); return Expression.Lambda<Func<T2, TResult>>( Expression.Invoke( expr, Expression.Constant(arg1, typeof(T1)), arg2 ), arg2); } public static Expression<Func<T1, TResult>> Bind2nd<T1, T2, TResult>(this Expression<Func<T1, T2, TResult>> expr, T2 arg2) { var arg1 = Expression.Parameter(typeof(T1), "arg1"); return Expression.Lambda<Func<T1, TResult>>( Expression.Invoke( expr, arg1, Expression.Constant(arg2, typeof(T2)) ), arg1); } }
微妙なのは生成される IL あたり.
そもそも Expression<TDelegate>.Compile による実行時コンパイルは基本的に最適化を行いません.たとえばこのコードの対応の素直なこと.
var expr = Expression.Lambda<Func<int>>( Expression.Subtract( Expression.Constant(5), Expression.Constant(5) ) ); var func = expr.Compile(); // 生成される IL IL_0000: ldc.i4.5 IL_0001: ldc.i4.5 IL_0002: sub IL_0003: ret
となれば,InvocationExpression が定数伝播とか関係なしに Hoisted Locals を導入しているように見えるのもそれほど不思議な話ではないと.
var baseExpr = (Expression<Func<int, int, int>>)((int x, int y) => x - y); var expr = baseExpr.Bind1st(5).Bind1st(5); var func = expr.Compile(); // 生成される IL IL_0000: ldarg.0 IL_0001: callvirt System.Object[] CreateHoistedLocals()/System.Runtime.CompilerServices.ExecutionScope IL_0006: stloc.0 IL_0007: ldloc.0 IL_0008: ldc.i4.0 IL_0009: ldc.i4.5 IL_000a: newobj Void .ctor(Int32)/System.Runtime.CompilerServices.StrongBox`1[System.Int32] IL_000f: stelem.ref IL_0010: ldc.i4.5 IL_0011: stloc.1 IL_0012: ldloc.0 IL_0013: ldc.i4.0 IL_0014: ldelem.ref IL_0015: castclass StrongBox`1 IL_001a: ldfld Int32 Value/System.Runtime.CompilerServices.StrongBox`1[System.Int32] IL_001f: stloc.2 IL_0020: ldloc.1 IL_0021: ldloc.2 IL_0022: sub IL_0023: ret
この IL が気に入らなければ (さすがにデリゲートを呼ぶたびに毎回配列確保はちょっと微妙),結局,自分で記号計算を書きましょうね,という話に.
私はというと,MetaLinq が Visual Studio 2008 RTM に対応するのを待っていたのですが,動きがなさげなのでそろそろ自分で書き始めてしまいそうな今日この頃.