【问题标题】:How can I combine several Expressions into a fast method?如何将多个表达式组合成一个快速方法?
【发布时间】:2010-03-10 17:24:10
【问题描述】:

假设我有以下表达式:

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

【问题讨论】:

  • 你希望能够写出类似的东西:Expression&lt;Action&lt;T, StringBuilder&gt;&gt; combinedExperssion = expr1 + expr2 + expr3;?

标签: c# linq lambda dynamicmethod


【解决方案1】:

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

  • 算术:Add、AddChecked、Divide、Modulo、Multiply、MultiplyChecked、Negate、NegateChecked、Power、Subtract、SubtractChecked、UnaryPlus
  • 创建:绑定、ElementInit、ListBind、ListInit、MemberBind、MemberInit、New、NewArrayBounds、NewArrayInit
  • 按位:与、ExclusiveOr、LeftShift (>)
  • 逻辑: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 表达式特别有趣。

【讨论】:

【解决方案2】:

你可以,但这不是小事。

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

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

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

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

【讨论】:

    【解决方案3】:

    在 4.0 中,这要容易得多,这要归功于树中对块操作的支持(尽管在 C# 表达式编译器中不支持)。

    但是,您可以利用StringBuilder 公开“流利”API 的事实来做到这一点;所以你有一个Func&lt;T,StringBuilder,StringBuilder&gt; 而不是Action&lt;T,StringBuilder&gt; - 如下所示(请注意,在这种情况下,表达这些表达式的实际语法是相同的):

    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 更适合您。

    【讨论】:

      【解决方案4】:

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

      编辑:

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

      另一种选择:

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

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

      【讨论】:

        【解决方案5】:

        看待这个问题的另一种方式是记住委托是多播的;您可以多次组合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; }
        
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-08-06
          • 2021-09-02
          • 2018-09-24
          • 2013-03-21
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多