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;
using System.Linq;
using System.Linq.Expressions;
namespace ExpressionDemo
{
class Program
{
static void Main(string[] args)
{
//create two parameter objects
ParameterExpression xParam = Expression.Parameter(typeof(int), "x");
ParameterExpression yParam = Expression.Parameter(typeof(int), "y");
//create an array from the objects, used when creating the lambda at runtime
ParameterExpression[] myParams = { xParam, yParam };
//create an add operation at runtime
BinaryExpression operation = Expression.Add(xParam,yParam);
//do a simple menu that allows the user to determine what code we write
Console.WriteLine("We are going to do something to 69 and 42...");
Console.WriteLine("Press m to multiply");
Console.WriteLine("Press a to add");
Console.WriteLine("Press s to subtract");
Console.WriteLine("Press d to divide");
ConsoleKeyInfo keyInfo = Console.ReadKey();
//based on user input, create the body of the expression
switch (keyInfo.KeyChar)
{
case 'm':
operation = Expression.Multiply(xParam, yParam);
break;
case 'a':
operation = Expression.Add(xParam, yParam);
break;
case 's':
operation = Expression.Subtract(xParam, yParam);
break;
case 'd':
operation = Expression.Divide(xParam, yParam);
break;
}
//we now have everything we need to create a runtime lambda expression
// let's do so
LambdaExpression simpleOpLambda = Expression.Lambda<Func<int, int, int>>(
operation,
myParams);
//ahh - this is where the magic is
// Compile on lambda methods creates a dynamic method
// at runtime. Whats cool about this is we build a data structure
// based on user input that, through Compile() turns into code at run time
int result = (int)simpleOpLambda.Compile().DynamicInvoke(69, 42);
//Show the result to the user.
Console.WriteLine("");
Console.WriteLine("Result = " + result);
Console.WriteLine("Press the 'any' key to exit...");
Console.ReadKey();
}
}
}
当然,这个示例并没有什么实用性,不过其所带来的意义却是我们没有通过发射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