代码改变世界

觉得有必要来澄清几组重要概念

2008-06-04 01:51 Jeffrey Zhao 阅读(...) 评论(...) 编辑 收藏

承蒙大家厚爱,不少朋友都会把遇到的问题向老赵进行讯问。不过在阅读很多朋友问题的过程中,以及平时和别人讨论中,亦或是园子里的文章中经常发现一些误用概念的情况。如果在概念上没有形成共识,那么在工作和交流上就会造成许多问题。因此,虽然老赵已经在其他一些地方(例如WebCast或一些文章的评论)做过解释,但是现在仍然觉得有必要特地来澄清一些概念,解释一下这些概念之间的区别和联系。希望在明确这些概念之后,大家能够把注意力集中在对于具体问题的分析解决上,而不要让沟通成为瓶颈。

在这篇文章中,我希望澄清三组概念,它们是:

  • AJAX / AJAX框架 / AJAX.NET (Professional) / ASP.NET AJAX
  • LINQ / LINQ to SQL / LINQ to XXX
  • Lambda Expression / Expression Tree / 匿名方法

AJAX / AJAX框架 / AJAX.NET (Professional) / ASP.NET AJAX

这是我见过的混淆最多的一组概念。不过当去年AJAX技术如火如荼,AJAX框架层出不穷,而微软又推出ASP.NET AJAX框架之后,这四个概念之间的混淆也愈发严重起来了。我经常能看到这样的问题:

  • 我在用AJAX技术时不知道怎么在UpdatePanel里……
  • 我用AJAX.NET框架调用Web Service时……

这是非常典型的概念混淆,上面两句话的AJAX与AJAX.NET都应该使用ASP.NET AJAX替换。关于这几个概念的区别我《深入浅出ASP.NET AJAX》系列WebCast多次进行解释,不过收效似乎并不明显。于是我打算在这里再进行一下说明:

AJAX是Asynchronous JavaScript and XML缩写。这个概念代表的是一种技术,当您在说“我在项目中使用了AJAX技术时”,只是代表了您使用客户端XMLHttpRequest对象与服务器端进行异步通信。不过因为随着AJAX技术的运用往往会带来丰富的客户端效果,因此对AJAX技术的广义理解也可以认为这是一种操作客户端DOM而带来丰富效果的技术(这个“广义”的描述其实并不准确,大家明白老赵的意思就可以了)。

AJAX框架是一套辅助开发人员使用AJAX技术的代码包(库,etc.)。一个AJAX框架的目的一般是对XMLHttpRequest对象的使用进行封装,并提供一些操作DOM元素或者实现特殊效果的“捷径”。成熟的AJAX框架有很多,它们可以被分为“以客户端为中心”和“面向特定服务器技术”两大类。前者的优秀代表有(排名不分先后):PrototypejQueryMootoolsYUIDojo等等(最后两个其实更接近一套客户端界面库,也就是“广义”的AJAX框架);而后者的典型代表既是AJAX.NET和ASP.NET AJAX了——当然,也有优秀的AJAX框架面向其他服务器技术,在此不提。

AJAX.NET (Professional)是ASP.NET平台上著名的AJAX框架,诞生于2005年2月(虽然正式命名为AJAX.NET则是在两个月之后),作者为Michael Schwarz。同年10月,Michael为AJAX.NET提供了更丰富的功能(主要是安全性方面),并将其改名为AJAX.NET Professional。AJAX.NET提供的最主要的(也是唯一)的功能就是异步调用服务器端方法,可谓非常纯粹的“基于数据”的AJAX使用方式。这个框架是一个个人作品,有支持.NET 1.1和2.0的版本,它并不属于微软官方,目前已经停止更新。

ASP.NET AJAX的Code Name为“Atlas”,在CTP向Beta版转移时曾经发生过翻天覆地的变化。ASP.NET AJAX中包含了UpdatePanel等控件,可以非常透明地为现有的ASP.NET WebForms应用程序添加AJAX效果。此外还提供了客户端异步调用Web Services的方法,使开发人员也能够使用面向数据的方式使用AJAX技术。值得一提的是ASP.NET AJAX的“附属品”相当丰富。例如ASP.NET AJAX名为“Microsoft AJAX Library”的客户端部分是一个纯客户端AJAX框架,提供了面向对象类型系统、浏览器兼容层、异步通信层等多种基础组件;ASP.NET AJAX的开源扩展包“AJAX Control Toolkit”包含了数十个可以直接使用的AJAX服务器端控件,这样开发人员能够轻松地添加丰富的客户端效果。同时,官方还为ASP.NET AJAX提供了“非正式”地扩展包,其中的History等优秀控件也将加入未来版本的ASP.NET AJAX框架中。ASP.NET AJAX是官方出品的AJAX框架,目前已经被集成到ASP.NET 3.5中去了,因此其版本号也从ASP.NET AJAX 1.0一下子“跃升为”ASP.NET AJAX 3.5。如果您看到了这些版本号也请不要疑惑,其实ASP.NET AJAX 3.5相对于ASP.NET AJAX 1.0来说只是修补了一些细小bug,几乎没有任何变化。

LINQ / LINQ to SQL / LINQ to XXX

LINQ是新生事物,不过从不少文章和讨论上看来,这方面的概念也已经有点混沌不清了。因此我们经常可以看到这样的话:

  • LINQ只能将数据表与实体属性一一对应……
  • LINQ开发指南:在LINQ中进行数据库字段映射……

以上两句话其实说的都是LINQ to SQL而不是指LINQ。可能由于LINQ to SQL的上镜率最广(连MSDN上About LINQ的第一个示例就是查询数据库的),因此许多人都将LINQ to SQL与LINQ混用,这会给初学者造成误解,认为LINQ就是LINQ to SQL,LINQ to SQL就是LINQ——事实当然不是这样的。

LINQ是Language-Integrated Query的缩写,是C# 3.0和VB 9.0中新加入的语言特性,可以在编程时使用内置的查询语言进行基于集合的操作。这么做可以大大简化开发过程,提高开发效率。例如:

List<User> userList = GetUserList();
var userWithOddId = from u in userList
                    where u.UserID % 2 == 1
                    select u;
 
foreach (User u in userWithOddId)
{
    Console.WriteLine(u.UserName);
}

如果没有LINQ,要筛选出ID为奇数的User对象则需要创建一个List,然后遍历整个列表,将符合特定条件的User对象放入新列表。而有了LINQ,这部分的筛选就变得非常容易,甚至只需要一句话就能完成。如果觉得这个例子不够说明LINQ对生产力有重大贡献的话,请关注我接下来的一篇文章(暂定名为《我们为什么要拥抱LINQ》)。LINQ特指形如上面这段代码中from...where...select这样的用法,其返回值是IQueryable<T>或IEnumerable<T>。

LINQ to SQL是.NET 3.5内置的一个轻量级O/R Mapping解决方案,可以将数据表映射为实体对象,方便开发人员对数据库的操作。可见,LINQ to SQL实只是LINQ的一个实现,提供了一个可以查询SQL Server数据库的LINQ Provider。

LINQ Provider是LINQ查询的执行器,标准LINQ语法支持许多的操作符,但是某个具体的LINQ实现可能只支持其中的一部分。在.NET 3.5默认提供了三种LINQ Provider,分别是LINQ to Object(即上面的例子),LINQ to SQL以及LINQ to XML。

LINQ to XXX表示使用LINQ针对XXX这种数据进行查询的解决方案。我们可以自定义LINQ Provider,使用我们自定义的查询规则来处理特定数据集。目前互联网上已经可以找到数十种LINQ Provider(如LINQ to Flickr,LINQ to NHibernate等),而已经处于beta 3阶段的ADO.NET Entity Framework,最终也会提供一个LINQ Provider,叫做“LINQ to Entities”。

Lambda Expression / Expression Tree / 匿名方法

Lambda Expression从定义上讲是指带有“=>”符号的表达式,例如:

  • x => x + 1
  • (x, y) => x > y
  • () => 5
  • (x, y) => { return x > y; }

Lambda Expreesion本身并不会在概念上引起混淆,不过由于在C# 3.0中Lambda Expression有两种截然不同的使用方式,有些朋友就会产生疑问,究竟Lambda Expression是做什么用的?

Lambda Expression的一个重要作用就是提供一种使用匿名方法的新语法,在《您善于使用匿名函数吗?》一文中您可以看到这种使用方式。利用Lambda Expression表示匿名函数的一个重要的缺点就是无法使用带out或ref关键字的参数,不过它比使用delegate关键字的表示法略为简单一点,因为无需提供参数类型,例如:

public static bool CallMethod(Func<int, bool> method)
{
    return method(0);
}
 
static void Main(string[] args)
{
    CallMethod(delegate(int a) { return false; });
    CallMethod(a => { return false; });
}

可见,使用Lambda Expression表示的匿名方法无需指定参数类型,因为这一切都已经交给编译器来判断了。这一点在参数类型长而复杂的情况下(例如并行库中的方法)非常重要。因此现在要不是会涉及到out/ref参数,我都会使用Lambda Expression来表示匿名方法。

Lambda Expression的另一个作用自然就是构造一个LambdaExpression对象。任意一个Expression对象都表示了一个Expression Tree的根节点,而开发人员可以通过解析这个Expression Tree来实现特定的功能。我们编写的方法可以接受一个Lambda Expression作为参数,但是我们还必须对这个参数的形式进行限制,这个参数才能有意义。这时候我们就会使用Expression<TDelegate>类型作为方法的参数类型,这样在使用这个方法时就必须使用满足TDelegate的签名及返回值的Lambda Expression才能编译通过。例如:

public static void CallMethod(Expression<Func<int, bool>> prediect) { ... }
 
static void Main(string[] args)
{
    CallMethod(a => a > 0);
}

解析一个Expression Tree并不是一件简单的事情,一定程度上这相当于在进行编译工作,只是最终生成的结果不是机器码或IL,而是一个执行结果,并且语法解析的过程已经由C#编译器帮我们完成了。我在《扩展LINQ to SQL:使用Lambda Expression批量删除数据》一文中曾经提到过这一点,并且给出了一个实例,感兴趣的朋友们可以参考一下。

需要注意的是,如果在构造一个Expression对象时,Lambda Expression的Body部分不能是Statement;而在表示一个匿名对象时Lambda Expression的Body既可以是Expression也可以是Statement。无论是使用Expression还是Statement作为Body,只要表示的含义相同,编译器都会生成一样的匿名函数。例如以下两种写法其实是等价的:

  • (x, y) => x > y
  • (x, y) => { return (x > y); }

 

最后,我留给大家一个问题:以下两个做法的结果是相同的,而代码也非常接近。不过它们其实有着非常大的区别,您能指出吗?

var intList = new List<int>() { 1, 2, 3, 4, 5 };
foreach (int i in intList.Where(i => i % 2 == 1))
{
    Console.WriteLine(i);
}
var intList = new List<int>() { 1, 2, 3, 4, 5 }.AsQueryable();
foreach (int i in intList.Where(i => i % 2 == 1))
{
    Console.WriteLine(i);
}