代码之家  ›  专栏  ›  技术社区  ›  chillitom Cee McSharpface

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

  •  6
  • chillitom Cee McSharpface  · 技术社区  · 16 年前

    假设我有以下表达式:

    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 回复  |  直到 16 年前
        1
  •  4
  •   Darin Dimitrov    16 年前

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

    • 算术:加法、加法检查、除法、模、乘法、多重检查、负数、负数检查、幂、减法、减法检查、一元加
    • 创建:bind,elementinit,listbind,listinit,memberbind,memberinit,new,newarraybounds,newarrayinit
    • 按位:and,exclusiveor,leftshift(<<),not,or,rightshift(>>)
    • 逻辑:Andalso(&&),条件(?:),等于,大于,大于或等于,小于或等于,*小于或等于,notequal,orelse(),typeis
    • 成员访问:arrayindex、arraylength、call、field、property、propertyorfield
    • 其他:convert、convertchecked、coalesce(??),常量,调用,lambda,参数,typeas,引号

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

    • 突变:addassign、addassignchecked、andassign、assign、dividesign、exclusiveorassign、leftshiftassign、moduloassign、multiplyasignchecked、orassign、postdecrementassign、postincrementassign、powerassign、precrementassign、preincrementassign、rightshiftassign、subtractassign,减去分配检查
    • 算术:递减、默认、递增、一个完成
    • 成员访问:arrayaccess,动态
    • 逻辑:referenceequal、referencenotequal、typeequal
    • 流:block、break、continue、empty、goto、ifthen、ifthenelse、iffalse、iftrue、label、loop、return、switch、switchcase、unbox、variable
    • 异常:catch、rethrow、throw
    • 调试:ClearDebugInfo、DebugInfo

    这个 Block 表情特别有趣。

        2
  •  1
  •   Dave Van den Eynde    16 年前

    你可以,但这不是一件小事。

    当您有一个expression类型的变量时,您可以检查它的body属性以查找表达式的数据结构。

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

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

    在这个非常小的例子中,我认为没有必要创建自己的LINQ提供者。你只需处理传递的表达式,然后从那里开始。但我怀疑你的申请比这要复杂一点。

        3
  •  1
  •   Marc Gravell    16 年前

    在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 更多的是你的油炸食品。

        4
  •  0
  •   leppie    16 年前

    您只能在.NET 4中执行此操作。对不起,不知道细节。

    编辑:

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

    另一种选择:

    创建“do”方法,即:

    void Do(params Action[] actions)
    {
      foreach (var a in actions) a();
    }
    
        5
  •  0
  •   Marc Gravell    16 年前

    查看此问题的另一种方法是记住委托是多播的;您可以将 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; }
    
    }