Magenic 公司的 Aaron Erickson 最近正在为 LINQ 编写一个扩展,叫做 Indexes for Objects ,即 i4o。简单说来,i4o 能够让我们对位于内存中的集合数据进行索引,并能够与 LINQ 配合使用。该项目是开源的,可以到 CodePlex 网站中下载。
Jonathan Allen (JA):是什么促使你开始 i4o 项目呢?
Aaron Erickson (AE):嗯,可以说这是个意外。在我曾经参与的一个项目中,程序需要在客户端处理位于内存中大量的集合数据——为购买大量药物产品的买家进行捆绑定价优化。当然,那时的平台还是.NET 2.0——LINQ 还没有出现,因此我自行实现了一些从这类集合中抽取某些数据的方法。并且,在实现这类“查找”方法时,我突然意识到若是集合中的数据都带有索引的话,那么定会极大提高程序的执行效率。于是我手工为这些集合中的一些字段添加了索引,最终让程序的运行时间非常令用户满意。
几乎在参与那个项目的同时,我也在为芝加哥.NET 用户组准备一个有关 LINQ 的演示。也是在一刹那间我有了这样的想法:正如数据库中的索引可以有效提高查找效率一样,为内存中的大规模集合添加索引也能让程序在执行效率方面受益匪浅。于是回想了一下我曾经以咨询顾问身份参与过的一些有关性能优化的项目,发现其中 90% 的改进都来自于为常见的查询请求添加恰当的索引。也正是在那时,让我产生了创建 i4o 项目的想法——用 i4o 创建添加了索引的集合,并让其能够与 LINQ 完美集成。
JA:看到你的 Blog 中曾经提到过表达式树(expression tree)。那么在使用表达式树的时候有什么困难么?表达式树主要是给普通开发者使用的,还是给 API 或类库开发者使用的呢?
AE:我个人有一些 LISP 背景——这可能让我在处理表达式树的时候能够轻松一些。我觉得其中最难以理解的部分可能就是需要反复试验才能明白这些 API 的使用方法。我也曾有过这样的想法:若是用 Ruby 或者 Lisp 之类动态语言的话,可能会容易不少——因为它省去了很多不同节点类型之间转型的过程。不过总体说来,一旦你的头脑中有了“用代码生成代码”的概念,那么想必理解、使用起来也不会太难。
对于普通开发者来讲,我觉得在 Orcas 发布之后,现在那些经常使用反射功能的开发者可能也将会经常使用表达式树的相关功能。这是一把双刃剑——我曾经看到很多滥用反射的例子(使用反射让程序看起来比较酷,而不是真的需要),出于同样的原因,表达式树也可能被滥用。不过也确实有些地方可以让表达式大显身手,例如在实现动态策略模式(dynamic strategy pattern,指需要在运行时调整策略的策略模式)时。
JA:能给出一个使用表达式树的例子吗?
AE:没问题——我们先从简单的入手——用表达式编写一个程序,根据用户的要求返回两个数的作用结果:
using System;<br></br> using System.Linq;<br></br> using System.Linq.Expressions;<p> namespace ExpressionDemo</p><br></br> {<br></br> class Program<br></br> {<br></br> static void Main(string[] args)<br></br> {<br></br> //create two parameter objects<br></br> ParameterExpression xParam = Expression.Parameter(typeof(int), "x");<br></br> ParameterExpression yParam = Expression.Parameter(typeof(int), "y");<br></br> //create an array from the objects, used when creating the lambda at runtime<br></br> ParameterExpression[] myParams = { xParam, yParam };<br></br> //create an add operation at runtime<br></br> BinaryExpression operation = Expression.Add(xParam,yParam);<br></br> //do a simple menu that allows the user to determine what code we write<br></br> Console.WriteLine("We are going to do something to 69 and 42...");<br></br> Console.WriteLine("Press m to multiply");<br></br> Console.WriteLine("Press a to add");<br></br> Console.WriteLine("Press s to subtract");<br></br> Console.WriteLine("Press d to divide");<p> ConsoleKeyInfo keyInfo = Console.ReadKey();</p><br></br> //based on user input, create the body of the expression<br></br> switch (keyInfo.KeyChar)<br></br> {<br></br> case 'm':<br></br> operation = Expression.Multiply(xParam, yParam);<br></br> break;<br></br> case 'a':<br></br> operation = Expression.Add(xParam, yParam);<br></br> break;<br></br> case 's':<br></br> operation = Expression.Subtract(xParam, yParam);<br></br> break;<br></br> case 'd':<br></br> operation = Expression.Divide(xParam, yParam);<br></br> break;<br></br> }<br></br> //we now have everything we need to create a runtime lambda expression<br></br> // let's do so<br></br> LambdaExpression simpleOpLambda = Expression.Lambda<Func<int, int, int>>(<br></br> operation,<br></br> myParams);<br></br> //ahh - this is where the magic is<br></br> // Compile on lambda methods creates a dynamic method<br></br> // at runtime. Whats cool about this is we build a data structure<br></br> // based on user input that, through Compile() turns into code at run time<br></br> int result = (int)simpleOpLambda.Compile().DynamicInvoke(69, 42);<br></br> //Show the result to the user.<br></br> Console.WriteLine("");<br></br> Console.WriteLine("Result = " + result);<br></br> Console.WriteLine("Press the 'any' key to exit...");<br></br> Console.ReadKey();<p> }</p><br></br> }<br></br> }
当然,这个示例并没有什么实用性,不过其所带来的意义却是我们没有通过发射 IL 而却同样在运行时生成了一个方法。
在 i4o 中,我们并不修改表达式树,但却需要使用到它们。当你在 LINQ 中书写 where 子句时,也就是在书写一条可被分析的表达式。而 i4o 需要确认的只有三点:第一,这是个等于表达式;第二,表达式的左边所指向的属性已经应用了索引;第三,表达式的右边或者是个常量,或者可以被计算为能够在索引中查找的值。若是满足了这些条件的话,那么 i4o 将从表达式中抽取出需要的信息,并开始执行 i4o 自己的查找逻辑。
JA:i4o 有没有将动态语言运行时(Dynamic Language Runtime)放在计划之列?
AE:当然了!i4o 近期的目标就是能够扩展支持到基于 DLR 的语言。不过问题在于我们还没决定怎样去实现。好在正如 Jim Hugunin 在近期的一篇 Blog( http://blogs.msdn.com/hugunin/archive/2007/05/15/dlr-trees-part-1.aspx )中所说的那样,DLR 表达式树在开发者眼中是静态类型的,不过在运行时将进行动态绑定。这个非常巧妙的实现——也就是通过在运行时动态求表达式树的值来判断是否可以使用索引——不单单是完全可行的,而且在当前的实现中就应该能够良好工作。
让 i4o 能够支持 DLR 的下一个问题就是如何让其能够支持 DLR 语言的属性(attribute)。上一次我查看项目进展时,发现对动态语言中 CLR 风格自定义属性的支持仍没有完全地解决。不过,若是微软公司提出了某种形式的对这些语言的声明式修饰(属性),那么也是件很自然的事。若是没有提供的话,那么通过在 IndexableCollection 中添加一些辅助设施来实现同样的功能(而不依赖于任何修饰)也不算困难。实际上,i4o 很有可能很快就会使用后者来实现该功能,这样使用者即可通过在运行时判断需要被索引的列来完成一定程度的优化(例如使用 CreateIndex(PropertyInfo propertyToAdd) 和 RemoveIndex(PropertyInfo propertyToRemove ) 等)。
JA:你觉得微软公司计划让 C#的匿名类不可变,并让 VB 的匿名类支持 Key 能够影响到 i4o 的设计吗?
AE:我感觉这些做法对 i4o 的影响不大,与现在处理被索引属性值变化所导致的问题差不多。如果在执行查询期间某个属性发生了变化,也就是索引发生了变化,且还提供了属性变化相关事件的话,那么索引也会相应地更新——查询仍会正常执行。当然——这也是我的想法——除非你想要查询的域恰好在查询过程中发生了变化——但我实在想象不出为什么要这样做。不过在这种情况下 i4o 的性能显然将无法达到最佳,因为改变属性值将导致索引被更新。不管怎样,如果这种处理 where 中属性变化的方式是由 C#中匿名类型将变成不可变所导致的话,我还是很有可能参考微软公司的方法进行设计。
我对 VB 团队引入 Key 的概念确实非常感兴趣。i4o 很有可能会直接分析出这些作为 Key 的域,并对其进行自动索引。
除了 i4o 之外,Aaron Erickson 还在负责 _ MetaLinq _ 项目,该项目用来维护表达式树。
查看英文原文: Aaron Erickson on LINQ and i4o
评论