如何将多个表达式组合成一个快速方法?

时间:2010-03-10 17:24:11

标签: c# linq lambda dynamicmethod

假设我有以下表达式:

Expression<Action<T, StringBuilder>> expr1 = (t, sb) => sb.Append(t.Name);
Expression<Action<T, StringBuilder>> expr2 = (t, sb) => sb.Append(", ");
Expression<Action<T, StringBuilder>> expr3 = (t, sb) => sb.Append(t.Description);

我希望能够将这些编译成与以下内容等效的方法/委托:

void Method(T t, StringBuilder sb) 
{
    sb.Append(t.Name);
    sb.Append(", ");
    sb.Append(t.Description);
}

最好的方法是什么?我希望它表现良好,理想情况下性能与上述方法相当。

更新 那么,虽然看起来没有办法在C#3中直接执行此操作,有没有办法将表达式转换为IL,以便我可以将它与System.Reflection.Emit一起使用?

5 个答案:

答案 0 :(得分:4)

不幸的是,在.NET 3.5中,您无法构建执行一系列任意操作的表达式。以下是支持的表达式列表:

  • 算术:Add,AddChecked,Divide,Modulo,Multiply,MultiplyChecked,Negate,NegateChecked,Power,Subtract,SubtractChecked,UnaryPlus
  • Creation:Bind,ElementInit,ListBind,ListInit,MemberBind,MemberInit,New,NewArrayBounds,NewArrayInit
  • 按位:并且,ExclusiveOr,LeftShift(&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&gt;
  • 逻辑:AndAlso(&amp;&amp;),条件(?:),Equal,GreaterThan,GreaterThanOrEqual,LessThan,* LessThanOrEqual,NotEqual,OrElse(||),TypeIs
  • 会员访问:ArrayIndex,ArrayLength,Call,Field,Property,PropertyOrField
  • 其他:Convert,ConvertChecked,Coalesce(??),Constant,Invoke,Lambda,Parameter,TypeAs,Quote

.NET 4通过添加以下表达式来扩展此API:

  • 变异:AddAssign,AddAssignChecked,AndAssign,Assign,DivideAssign,ExclusiveOrAssign,LeftShiftAssign,ModuloAssign,MultiplyAssign,MultiplyAssignChecked,OrAssign,PostDecrementAssign,PostIncrementAssign,PowerAssign,PreDecrementAssign,PreIncrementAssign,RightShiftAssign,SubtractAssign,SubtractAssignChecked
  • 算术:递减,默认,增量,OnesComplement
  • 会员访问:ArrayAccess,动态
  • 逻辑:ReferenceEqual,ReferenceNotEqual,TypeEqual
  • Flow:Block,Break,Continue,Empty,Goto,IfThen,IfThenElse,IfFalse,IfTrue,Label,Loop,Return,Switch,SwitchCase,Unbox,Variable
  • 例外:Catch,Rethrow,Throw
  • 调试:ClearDebugInfo,DebugInfo

Block表达特别有趣。

答案 1 :(得分:1)

你可以,但这不是一项微不足道的任务。

如果您有Expression类型的变量,则可以检查其Body属性以查找表达式的数据结构。

你不能要求编译器为你编译它,因为它不会得到你想要的结果。你必须解析所有表达式的主体,并以某种方式将它们组合成一个单独的方法,所有这些都是通过同时发出IL(或者通过生成C#并且如果你觉得IL是一个步骤太过而编译的那样)。

正如LINQ-to-SQL将表达式编译为SQL查询一样,您也可以将表达式编译成您需要的任何表达式。你将面临很多工作,但你只需要实现你想要支持的东西。

在这个相当简单的案例中,我认为没有必要创建自己的LINQ提供程序。您可以使用表达式传递并从那里开始。但我怀疑你的应用程序比这复杂一点。

答案 2 :(得分:1)

在4.0中,由于树中对块操作的支持(尽管不在C#表达式编译器中),因此这更容易。

但是,你可以通过利用StringBuilder暴露“流畅”API的事实来做到这一点;所以代替Action<T,StringBuilder>你有一个Func<T,StringBuilder,StringBuilder> - 如下所示(请注意,在这种情况下,表达这些表达式的实际语法相同):

class Program
{
    static void Main()
    {
        Foo(new MyType { Name = "abc", Description = "def" });
    }
    static void Foo<T>(T val) where T : IMyType
    {
        var expressions = new Expression<Func<T, StringBuilder, StringBuilder>>[] {
                (t, sb) => sb.Append(t.Name),
                (t, sb) => sb.Append(", "),
                (t, sb) => sb.Append(t.Description)
        };
        var tparam = Expression.Parameter(typeof(T), "t");
        var sbparam = Expression.Parameter(typeof(StringBuilder), "sb");

        Expression body = sbparam;
        for (int i = 0; i < expressions.Length; i++)
        {
            body = Expression.Invoke(expressions[i], tparam, body);
        }
        var func = Expression.Lambda<Func<T, StringBuilder, StringBuilder>>(
            body, tparam, sbparam).Compile();

        // now test it
        StringBuilder sbInst = new StringBuilder();
        func(val, sbInst);
        Console.WriteLine(sbInst.ToString());
    }
}
public class MyType : IMyType
{
    public string Name { get; set; }
    public string Description { get; set; }
}
interface IMyType
{
    string Name { get; }
    string Description { get; }
}

肯定可能来检查树并手动发出IL(或许DynamicMethod),但你必须做出一些限制复杂性的决定。对于所呈现的代码,我可以在合理的时间内完成它(仍然不是一件容易的事),但如果你期望任何更复杂的Expression更多你的话。

答案 3 :(得分:0)

你只能在.NET 4中这样做。很抱歉不知道细节。

编辑:

如果您对Reflection.Emit感到满意,则可以发出一个按顺序调用这些表达式的方法。

另一种选择:

创建'do'方法,即:

void Do(params Action[] actions)
{
  foreach (var a in actions) a();
}

答案 4 :(得分:0)

另一种看待这个问题的方法是记住委托是多方的;你可以多次组合Action;

class Program
{
    static void Main()
    {
        Foo(new MyType { Name = "abc", Description = "def" });
    }

    static void Foo<T>(T val) where T : IMyType {
        var expressions = new Expression<Action<T, StringBuilder>>[] {
                (t, sb) => sb.Append(t.Name),
                (t, sb) => sb.Append(", "),
                (t, sb) => sb.Append(t.Description)
        };
        Action<T, StringBuilder> result = null;
        foreach (var expr in expressions) result += expr.Compile();
        if (result == null) result = delegate { };
        // now test it
        StringBuilder sbInst = new StringBuilder();
        result(val, sbInst);
        Console.WriteLine(sbInst.ToString());
    }
}
public class MyType : IMyType
{
    public string Name { get; set; }
    public string Description { get; set; }
}
interface IMyType
{
    string Name { get; }
    string Description { get; }

}