Expression的限制——及展望
到目前为止,Expression的表现几近完美——然而,还有LINQ表达式的构造方式还有许多明显的限制:
- 没有内置的机制可以改变对象的属性/字段。
- 没有方法可以执行一系列的操作
在上面的例子中,我们可以使用AndAlso将多个步骤连接成一个逻辑语句。然而,在.NET 3.5中无法使用一个表达式树来表现如下的语句:
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 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 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为数据库构造一个表达式树,就好比:
myDatabase.Customers
.Where(c => c.Region == "North")
.Update(c => {
c.Manager = "Fred";
c.Priority = c.Priority + 10;
});
再次强调这只是“假如”——这需要在LINQ提供者上耗费大量的工作,但是我们从中可以看出:理论上说,一个Expression对象是有能力来封装我们的意图的(更新实体的多个属性)。可惜,无论是语言还是框架都还有很长的路要走,只有时间可以说明一切。