読者です 読者をやめる 読者になる 読者になる

Expression Trees: Bind1st, Bind2nd

.NET

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 が気に入らなければ (さすがにデリゲートを呼ぶたびに毎回配列確保はちょっと微妙),結局,自分で記号計算を書きましょうね,という話に.
私はというと,MetaLinqVisual Studio 2008 RTM に対応するのを待っていたのですが,動きがなさげなのでそろそろ自分で書き始めてしまいそうな今日この頃.