【IT168 技术】无论你是否喜欢反射,很多情况下你不可避免地会需要在运行时(而不是编译时)访问一个类型的成员。可能你在尝试着编写一些验证、序列化或是ORM代码,也可能必要的属性或方法是在运行时从配制文件或数据库中获得的。无论是什么原因,你在某些时候一定写过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的关键部分之一(尤其是在访问数据库等外部资源的时候)是将代码表现为表达式树的概念。这种方法的可用领域非常广泛,例如我们可以这样筛选数据:
where cust.Region == "North"
select cust;
虽然从代码上看不太出彩,但是它和下面使用Lambda表达式的代码是完全一致的:
LINQ以及Where方法细节的关键之处,便是Lambda表达式。在LINQ to Object中,Where方法接受一个Func类型的参数——它是一个根据某个对象(T)返回true(表示包含该对象)或false(表示排除该对象)的委托。然而,对于数据库这样 的数据源来说,Where方法接受的是Expression<Func<T, bool>>参数。它是一个表示测试规则的表达式树,而不是一个委托。
这里的关键点,在于我们可以构造自己的表达式树来应对各种不同场景的需求——表达式树还带有编译为一个强类型委托的功能。这让我们可以在运行时轻松编写IL。