6

假设我有以下表达式:

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 一起使用?

4

5 回答 5

4

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

  • 算术:加法、加法检查、除法、取模、乘法、乘法检查、否定、否定检查、幂、减法、减法检查、一元加法
  • 创建:Bind、ElementInit、ListBind、ListInit、MemberBind、MemberInit、New、NewArrayBounds、NewArrayInit
  • 按位:与、ExclusiveOr、LeftShift (<<)、Not、Or、RightShift (>>)
  • 逻辑:AndAlso (&&)、条件 (? :)、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
  • 流:阻塞、中断、继续、空、转到、IfThen、IfThenElse、IfFalse、IfTrue、标签、循环、返回、开关、SwitchCase、拆箱、变量
  • 例外:接球、再掷、掷
  • 调试:ClearDebugInfo、DebugInfo

Block表达式特别有趣。

于 2010-03-14T13:37:27.290 回答
1

你可以,但这不是一项简单的任务。

当你有一个表达式类型的变量时,你可以检查它的 Body 属性来找到表达式的数据结构。

您不能要求编译器为您编译它,因为它不会得到您想要的结果。您必须解析所有表达式的主体,并以某种方式将它们组合成一个方法,所有这些都通过同时发出 IL 来实现(或者,通过生成 C# 并在您觉得 IL 走得太远的情况下编译它)。

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

在这个相当微不足道的情况下,我认为没有必要创建自己的 LINQ 提供程序。您可以只使用传递的表达式并从那里开始。但我怀疑您的应用程序比这更复杂一些。

于 2010-03-18T11:53:05.660 回答
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更麻烦了。

于 2010-03-20T08:43:54.790 回答
0

您只能在 .NET 4 中执行此操作。抱歉,不知道详细信息。

编辑:

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

另一种选择:

创建一个'do'方法,即:

void Do(params Action[] actions)
{
  foreach (var a in actions) a();
}
于 2010-03-10T17:32:06.000 回答
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; }

}
于 2010-03-20T09:18:10.480 回答