技术开发 频道

表达式即编译器

  Expression的限制——及展望

  到目前为止,Expression的表现几近完美——然而,还有LINQ表达式的构造方式还有许多明显的限制:

  •   没有内置的机制可以改变对象的属性/字段。
  •   没有方法可以执行一系列的操作

  在上面的例子中,我们可以使用AndAlso将多个步骤连接成一个逻辑语句。然而,在.NET 3.5中无法使用一个表达式树来表现如下的语句:

person.DateOfBirth = newDob;

person.Name
= newName;

person.UpdateFriends();

person.Save();

  我们之前看到的Bind操作只能在创建新对象时使用。我们可以获取setter方法,但是我们无法将多个方法调用串联起来(就像“流畅”API那样,可惜目前没有)。

  幸运的是,.NET 4.0扩展了Expression API,增加了新的类型和方法。这么做的目的是支持动态语言运行时(DLR,Dynamic Language Runtime)。这大大扩展了:

  •   修改操作:AddAssign, AddAssignChecked, AndAssign, Assign, DivideAssign, ExclusiveOrAssign, LeftShiftAssign, ModuloAssign, MultiplyAssign, MultiplyAssignChecked, OrAssign, PostDecrementAssign, PostIncrementAssign, PowerAssign, PreDecrementAssign, PreIncrementAssign, RightShiftAssign, SubtractAssign, SubtractAssignChecked
  •   算术操作:Decrement, Default, Increment, OnesComplement
  •   成员访问:ArrayAccess, Dynamic
  •   逻辑运算:ReferenceEqual, ReferenceNotEqual, TypeEqual
  •   逻辑流:Block, Break, Continue, Empty, Goto, IfThen, IfThenElse, IfFalse, IfTrue, Label, Loop, Return, Switch, SwitchCase, Unbox, Variable
  •   异常操作:Catch, Rethrow, Throw
  •   调试:ClearDebugInfo, DebugInfo

  这对编写动态代码来说可谓是个天大的好消息(它甚至包含了将代码编译为MethodBuilder的能力,可生成动态类型)。例如,我们可以用它来编写一个简单的for循环来打印数字0到9:

var exitFor = Expression.Label("exitFor"); // jump label
            var x = Expression.Variable(typeof(int), "x");

            var body
= Expression.Block(new[] { x }, // declare scope variables
                    Expression.Assign(x,

                        Expression.Constant(
0, typeof(int))), // init
                    Expression.Loop(

                        Expression.IfThenElse(

                            Expression.GreaterThanOrEqual(
// test for exit

                                x,

                                Expression.Constant(
10, typeof(int))

                            ),

                            Expression.Break(exitFor),
// perform exit
                            Expression.Block( // perform code

                                Expression.Call(

                                    
typeof(Console), "WriteLine", null, x),

                                Expression.PostIncrementAssign(x)

                            )

                        ), exitFor));



            var runtimeLoop
= Expression.Lambda<Action>(body).Compile();

  虽然看上去似乎不是那么一鸣惊人,但如果您要在运行时编写编译好的代码,它比其他方式要方便和灵活得多。

  之前我们通过属性复制来克隆一个对象——不过现在我们已经有能力把一个对象的数据复制给另一个对象了,这点对于ORM工具来说非常有用,例如跟踪一个对象的状态,执行外部更新,再提交这些改变。我们可以这么做:

            var source = Expression.Parameter(typeof(T), "source");

            var dest
= Expression.Parameter(typeof(T), "dest");



            
// for each read/write property, copy the source's value            // into the destination object

            var body
= Expression.Block(

                from prop
in typeof(T).GetProperties(

                    BindingFlags.Public
| BindingFlags.Instance)

                
where prop.CanRead && prop.CanWrite

                select Expression.Assign(

                    Expression.Property(dest, prop),

                    Expression.Property(source, prop)));



            var copyMethod
= Expression.Lambda<Action<T,T>>(

                body, source, dest).Compile();

  使用表达式在运行时创建类型

  .NET 4.0中有个有趣的功能:在此之前我们可以把一个Expression编译为一个独立的委托,而如今你可以在动态创建新类型时,使用 CompileToMethod方法将一个表达式树作为方法体提供给一个MethodBuilder对象。这意味着,在未来我们可以方便地编写功能丰富的 类型(包括多态和接口实现),而不用直接接触IL。

  语言中的高级表达式

  尽管运行时已经支持非常成熟的表达式树了,但是在C# 4.0中并没有额外的语言支持——所以我们只能手动创建表达式。这一般不会成为问题,因为几乎没有LINQ提供者会支持这些复杂表达方式。如果你在编译期 知道这些类型,不如直接编写普通的方法或匿名方法。不过这也为4.0以后的版本留下了想象空间。例如(只是推测),想象一下如果语言可以使用 Expression.Assign和Expression.Block为数据库构造一个表达式树,就好比:

        // imaginary code; this doesn’t work

        myDatabase.Customers

             .Where(c
=> c.Region == "North")

             .Update(c
=> {

                 c.Manager
= "Fred";

                 c.Priority
= c.Priority + 10;

             });

  再次强调这只是“假如”——这需要在LINQ提供者上耗费大量的工作,但是我们从中可以看出:理论上说,一个Expression对象是有能力来封装我们的意图的(更新实体的多个属性)。可惜,无论是语言还是框架都还有很长的路要走,只有时间可以说明一切。

0
相关文章