技术开发 频道

表达式即编译器

  【IT168 技术】无论你是否喜欢反射,很多情况下你不可避免地会需要在运行时(而不是编译时)访问一个类型的成员。可能你在尝试着编写一些验证、序列化或是ORM代码,也可能必要的属性或方法是在运行时从配制文件或数据库中获得的。无论是什么原因,你在某些时候一定写过GetType() ——就像这样:

Type type = obj.GetType();
foreach (var property in type.GetProperties())

{

    Console.WriteLine(
"{0} = {1}",

       property.Name,

       property.GetValue(obj,
null));

}

  虽说这个普通的示例可以正常工作,但它不够理想——这里有一些关键性的问题:

  •   它比较慢——你偶尔使用的话还好,但是在密集的循环中就会有明显的差距了。
  •   没有比较方便的做法把那些准备工作“打包”成可复用的代码。

  而且,在动态代码的需求变复杂时,情况则会更加糟糕。

  当然,一个明显的应对方式是“不要使用反射”——例如,手动(或使用代码生成工具)为每个类型写一个方法来做对应的事情。表面看来这种简单的做法没有问题,但是这会导致大量的重复代码,而且也无助于我们使用编译期间还不可知的类型。

  我们需要的是某种形式的元编程(meta-programming)API。幸运的是,在.NET 2.0中提供Reflection.Emit来动态编写IL(Java“二进制码(bytecode)”在.NET中的对应物),不过这也要求开发人员直接处理所有细微的装箱、类型转换、调用堆栈细节或操作符“提升”等操作——简而言之,你需要了解大量本不需要知道的CLI内容。另一个选择是 CodeDom,不过这也是一种显式的代码生成方式,我们想要的做的事情往往会淹没在大量构造CodeDom所需要的繁琐事务中。

  我们需要的是一种折衷的方案,一种足够高的抽象让我们不必关心实际的IL如何,但也不能过于高级:我们的代码想要尽可能的简单并富于表现力。

  背景:走近System.Linq.Expressions.Expression

  微软在.NET 3.5中引入了LINQ。LINQ的关键部分之一(尤其是在访问数据库等外部资源的时候)是将代码表现为表达式树的概念。这种方法的可用领域非常广泛,例如我们可以这样筛选数据:

var query = from cust in customers

            
where cust.Region == "North"

            select cust;

  虽然从代码上看不太出彩,但是它和下面使用Lambda表达式的代码是完全一致的:

var query = customers.Where(cust => cust.Region == "North");

  LINQ以及Where方法细节的关键之处,便是Lambda表达式。在LINQ to Object中,Where方法接受一个Func类型的参数——它是一个根据某个对象(T)返回true(表示包含该对象)或false(表示排除该对象)的委托。然而,对于数据库这样 的数据源来说,Where方法接受的是Expression<Func<T, bool>>参数。它是一个表示测试规则的表达式树,而不是一个委托。

  这里的关键点,在于我们可以构造自己的表达式树来应对各种不同场景的需求——表达式树还带有编译为一个强类型委托的功能。这让我们可以在运行时轻松编写IL。

0
相关文章