我也可以用它来检查一个特定的ASP.NET控件是否在一个容器控件集合里:
我甚至可以将其用在象整数这样的标量数据类型上:
注意上面,你甚至可以在象整数值42这样的基本数据类型值上使用扩展方法。因为CLR支持数值类型的自动boxing/unboxing,扩展方法可以直接使用在数值和其他标量数据类型上。
你大概可以开始从上面的例子中看出,扩展方法可以促成一些非常丰富和描述性强的扩展性使用场景。当使用于.NET中常见的基类和接口上时,他们可以促成一些非常好的特定于某个领域(domain specific)的框架和组合使用场景。
内置的System.Linq扩展方法
一个在Orcas时段随.NET发布的内置的扩展方法库是一套允许开发人员对任何数据进行查询的非常强有力的查询扩展方法。这些扩展方法实现位于新的 System.Linq 命名空间之下,定义了标准的查询操作符扩展方法,可以为.NET开发人员用来轻松地查询XML,关系数据库,.NET 对象, 和任何其他数据结构类型。
下面是使用这些查询扩展方法的扩展性模型的几个好处:
1) 它允许一个可用于所有数据类型(数据库,XML文件,内存中的对象,以及web-services等)的共同的查询编程模型和语法。
2) 它是可以组合的,允许开发人员轻松地往查询语法中添加新的方法/操作符。譬如,我们可以将我们自定义的“In()”方法与为LINQ所定义的标准的“Where()”方法作为一个单独查询的一部分一起使用。我们自定义的In()方法看上去就跟由System.Linq命名空间提供的标准方法一样。
3) 它是可扩展的,允许与任何数据提供器类型一起使用。譬如,任何一个象NHibernate或LLBLGen这样现有的ORM引擎可以实现LINQ的标准查询操作符来允许对他们现有的ORM实现和映射引擎实现LINQ查询。这允许开发人员学会一个查询数据的共同方式,然后对种类繁多的丰富数据存储实现使用同样的技能。
我将在下几个星期里对LINQ作更多的示范,但想留给你几个例子,这些例子展示了如何对不同类型的数据使用几个内置的LINQ查询扩展方法:
使用场景一:对内存中的.NET对象使用LINQ扩展方法
假定我们象这样定义了代表“Person”的类:
然后我可以使用新的对象初始化器和集合初始化器特性创建和填充一个“people”集合,象这样:
然后我可以使用由System.Linq提供的标准的“Where()”扩展方法来获取这个集合中FirstName的首字符是\的那些“Person”对象,象这样:
上面这个新的 p => 语法是“Lambda表达式”的一个例子,是对C# 2.0匿名方法支持的更简明的发展,允许我们通过一个实参来轻松地表达查询过滤(在这个情形下,我们表示我们只想要返回一串firstname属性的首字符是“S”字母的Person对象) 。上面这个查询然后就会返回包含2个对象的序列,Scott 和 Susanne。 我也可以利用由System.Linq提供的新的“Average” 和“Max”扩展方法编写代码来决定我的集合里的人的平均年龄,以及年龄最大的人,象这样:
使用场景二:对XML文件使用LINQ扩展方法
你手工在内存里创建一个硬写(hard-coded)的数据集合大概是很少见的。更有可能的是,你会从一个XM文件,数据库,或web服务里获取数据。
假定我们在硬盘上有一个XML文件,包含下面这样的数据:
很明显地,我可以使用现有的 System.Xml APIs 来装载这个XML文件进一个DOM,然后访问它,或者使用一个层次较低的XmlReader API ,自己对之手工分析。或者,在 Orcas中,我现在也可以使用支持标准的LINQ扩展方法的System.Xml.Linq 实现(即 XLINQ),更优雅地分析和处理XML。
下面的代码例子展示了如何使用LINQ来获取所有包含一个子节点的值的首字母为“S”的
注意,它使用了跟内存中对象例子中一模一样的 Where() 扩展方法。现在它返回一个“XElement”元素序列,XElemen是没有类型的XML节点元素。或者我也可以重写查询表达式,通过LINQ的 Select() 扩展方法来构造数据形状,提供一个使用了新的对象初始化器句法的Lambda 表达式来填充同样的“Person”类,跟我们第一个内存中的集合的例子一样:
上面的代码会做需要打开,分析,和过滤XML,然后返回一个强类型的Person对象序列所有的工作,不需要什么映射或持久的文件来映射数值,我只是在上面的LINQ查询式里直接指明了从XML到对象的构形而已。
我也可以和前面一样使用同样的Average() 和 Max() LINQ扩展方法来计算XML文件中
我不用手工分析XML文件,XLINQ 不仅可以为我处理分析,它在估算LINQ表达式时,也可以使用低层的XMLReader,而不是使用DOM来分析文件。这意味着它是迅速之极,而且不分配很多内存。
使用场景三:对数据库使用LINQ扩展方法
假定我们拥有一个SQL数据库,内含一个叫“People”的表,具有下列数据定义:
我可以使用Visual Studio中新的LINQ到SQL的所见即所得(WYSIWYG) ORM设计器,快速地创建一个
使用LINQ to SQL (第一部分)(韩现龙译)
【原文地址】Using LINQ to SQL (Part 1)
【原文发表日期】 Saturday, May 19, 2007 12:41 AM
在过去的几个月中我写了一系列涵盖了VS和.NET Framework Orcas版中的一些新特性的帖子,下面是这些帖子的链接:
? ? ? ? ?
自动属性,对象初始化器,和集合初始化器 扩展方法 Lambda表达式 查询句法 匿名类型
以上的语言特性帮助数据查询成为一等编程概念。我们称这个总的查询编程模型为“LINQ”--它指的是.NET语言级集成查询。
开发者可以在任何的数据源上使用LINQ。他们可以在他们选择的编程语言中表达高效的查询行为,选择将查询结果转换或构形成他们想要的任何格式,然后非常方便地操作这个结果集。有LINQ功能的语言可以提供完全的类型安全和查询表达式的编译时检查,而开发工具则可以提供在编写LINQ代码时完全的智能感知,调试,和丰富的重构支持。
LINQ支持一个非常丰富的的扩展性模型,该模型将有助于对不同的数据源生成不同的高效运算因子(domain-specific operators)。.NET Framework的Orcas版本将发布一些内置库,这些库提供了针对对象(Objects),XML,和数据库的LINQ支持。
什么是LINQ to SQL?
LINQ to SQL 是随.NET Framework Orcas版一起发布的O/RM(对象关系映射)实现,它允许你用.NET 的类来对一个关系型数据库建模。然后你可以用LINQ对数据库进行查询,以及进行更新/插入/删除数据的操作。
LINQ to SQL完全支持事务,视图和存储过程。它还提供了一种把数据验证和业务逻辑规则结合进你的数据模型的便利方式。
使用LINQ to SQL对数据库建模:
Visual Studio Orcas版中提供了一个LINQ to SQL设计器,该设计器提供了一种简易的方式,将数据库可视化地转换为LINQ to SQL对象模型。我下一篇博客将会更深入一些来介绍怎么使用该设计器(你可以观看我一月份时录制的这个关于如何从头开始创建LINQ to SQL模型的录像)。
通过LINQ to SQL设计器,我可以方便地设计出如下所示的Northwind数据库的示例模型:
上图定义了四个实体类:Product, Category, Order 和 OrderDetail。每个类的属性都映射到数据库中相应表的字段,类实体的每个实例代表了数据表中的一行记录。
在上图中,四个实体类间的箭头代表了不同实体间的关联/关系。它们主要是根据数据库中的主键/外键关系生成的。设计器上的箭头的指向表明了该关系是一对一还是一对多的关系。基于此,强类型的属性将会被加入到实体类中。例如,上边的Category类和Product类之间有一个“一对多”的关系。这意味着Category类将有一个\属性,该属性代表了在该类中所有的产品对象的集合。而Product类将会有一个\属性来指向一个Category类的实例,该Category类的实例表明了了产品所属的类别。 上图中LINQ to SQL设计器的右侧列出了与我们的数据库模型交互的存储过程。在上边的例子中,我添加了一个“GetProductsByCategory”存储过程。它有一个categoryID作为输入参数,返回一个产品实体序列作为结果集。下面的代码将展示如何调用该存储过程。 理解DataContext类
当你点击LINQ to SQL设计器上的“保存\按钮时,Visual Studio将会保存我们建立的代表了实体和数据库关系的各个类。针对加入到我们的解决方案的每一个LINQ to SQL设计器文件,同时也会生成一个自定义的DataContext类。这个DataContext类是我们从数据库中查询实体或者进行更改操作的主要渠道。生成
的DataContext类将含有一些属性,对应于我们在数据库中建了模的每个数据表,以及一些方法,对应于我们添加的每个存储过程。
例如,下图就是基于我们上边设计的模型而生成的的NorthwindDataContext类:
LINQ to SQL 代码例子
用LINQ to SQL 设计器对我们的数据库建模之后,我们就可以很方便地编写代码对数据库进行操作了。下边是一些展示了常见的数据库操作的代码例子:
1) 从数据库中查询Products 下面的代码用LINQ to SQL 查询语法来获取Product对象的IEnumerable序列。注意代码是如何通过Product/Category关系来仅查出那些类别是\的产品的: C#:
VB:
2) 更新数据库中的一条产品记录
下面的代码示范了如何从数据库中查询出单一的一条产品记录,更新它的价格,然后将改动保存至数据库: C#:
VB:
注意:VB在Orcas Beta1中尚不支持Lambda。但是在Beta2中,它就会支持了--那时代码就会能写得更为简洁一些。
3) 向数据库中插入一条新的Category和两条新的Products 下面的代码示范了如何生成一个新的分类,然后生成两条和该分类相关联的产品,然后将这三条记录保存到数据库中。
注意下边,我不用手工去维护主/外键关系,取而代之的是,我只向分类对象的“Products”集合中添加了两个Product记录,然后把该Category对象添加到DataContext的“Categories”集合中,LINQ to SQL将知道自动为我持久适当的PK/FK的关系。 C#
VB:
4) 从数据库中删除Products
下面的代码示范了如何从数据库中删除所有的玩具产品: C#:
VB:
5) 调用存储过程
下面的代码示范了如何不使用LINQ查询语法,而是通过调用我们在上面向数据模型中添加的“GetProductsByCategory”存储过程来查询Product实体。注意,一旦我查询出了Product结果集,我可以更新/删除它们,然后再调用 db.SubmitChanges()来将这些更新提交到数据库。 C#:
VB:
6) 在服务器端分页查询Products 下面的代码示范了如何通过LINQ查询语法实现高效的服务器端数据库分页查询。通过使用下面的Skip()和Take()操作符,我们从数据库中只查询出从200行开始的10条记录: C#:
VB:
总结
LINQ to SQL提供了一种很棒的、干净利索的方法来为你的应用程序来建立数据层。一旦你定义了数据模型,你就可以方便而且有效地对它进行查询,插入,更新和删除。
希望以上的介绍和代码例子刺激了你的胃口,希望了解到更多东西。在接下来的几周里我会在该系列中更具体地探讨LINQ to SQL。
Orcas中C#语言的新特性:自动属性,对象初始化器,和集合初始化器
【原文地址】New C# \Language Features: Automatic Properties, Object Initializers, and Collection Initializers
【原文发表日期】 Thursday, March 08, 2007 11:01 PM
上个星期,我们发布了我们的Visual Studio和.NET框架Orcas版三月份的CTP,这是个谁都可以下的免费下载,同时提供VPC镜像(允许你在一个虚拟机里运行它)以及单独的安装文件(注:如果你在运行Vista的话,你要确认你只使用VPC版本)。你可以在这里下载。
几个星期前,我曾在博客上讨论过Orcas中针对ASP.NET开发人员的一些重大的改进。如果你还没有读过这个贴子的话,我强烈建议你在这里读一下。我认为你会非常喜欢该帖子讨论的新特性的。
除了那些框架和工具类的精彩新特性外,我认为开发人员(包括所有的.NET应用类型的开发人员)会非常喜爱Orcas的一件事情是,VB和C#将包含一些新语言特性和改进。这些语言改动将以既微妙又深刻的方式改进我们的开发体验,势将改进效率,减小我们需要键入的代码量。
在下几个星期里,我将试着在博客里讨论几个这些语言的改进之处,示范如何把它们使用在一起,来产生一些非常强有力的结果。
新的C#语言特性:自动属性(Automatic Properties)
如果你现在是C#开发人员的话,你大概非常习惯编写象下面这个代码片段一样带有基本属性的类型:
public class Person { private string _firstName; private string _lastName; private int _age; public string FirstName { get { return _firstName; } set { _firstName = value; } } public string LastName { get { return _lastName; } set { _lastName = value; } } public int Age { get { return _age; } set { _age = value; } } } 注意,我们在属性的geter/setter中实际上并没有添加什么逻辑,我们只是将get/set实施到了一个成员变量。我们不禁要问这样一个问题:为什么不直接使用成员变量而使用属性呢?这是因为,向外面呈现公开的成员变量有很多不好的地方。二个最大的问题是:1) 你无法轻易地对成员变量做数据绑定,2) 如果你从类中向外呈现成员变量的话,之后,你不重新编译那些引用老的类的任何程序集,就无法将它们改成属性(譬如,要添加验证逻辑到setter里)。
Orcas中发布的新C#编译器通过一个叫“自动属性(automatic properties)”的语言特性提供了一个优雅的方式来使得你的编码更加简洁,同时还保持属性的灵活性。自动属性允许你避免手工声明一个私有成员变量以及编写get/set逻辑,取而代之的是,编译器会自动为你生成一个私有变量和默认的get/set 操作。 譬如,使用自动属性,我现在可以将上面的代码改写成:
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } } 或者,我想更简明的话,我可以将空白的地方做进一步压缩,象这样:
public class Person { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } } 当Orcas版中的C#编译器遇上象上面这样的空的get/set属性的话,它会自动为你在类中生成一个私有成员变量,对这个变量实现一个公开的getter 和setter。这么做的好处是,从类-合同(type-contract)的角度来看,这个类跟我们上面第一个有点冗长的实现看上去完全一样,这意味着,不象公开的成员变量,在将来,我可以在我的属性setter实现中添加验证逻辑,而不用对引用我的类的任何外部组件做改动。 Bart De Smet对使用Orcas三月份CTP版中的自动属性时内部发生的情形作了精彩的描述,你可以在这里阅读他的精彩相关帖子。
C#和VB语言的新特性:对象初始化器(Object Initializers)
.NET框架中的类型非常依赖于属性的使用。当生成对象实例和使用新的类型时,写出象下面这样的编码是非常常见的情形:
Person person = new Person(); person.FirstName = \; person.LastName = \; person.Age = 32; 你有没有想要把这样的编码简化过(也许将其安排在一行上)?使用Orcas中的C#和VB语言编译器的话,你现在可以利用一个称为“对象初始化器(object Initializers)”的“语法甜头(syntactic sugar)”语言特性来做些简化,将上述代码重写为:
Person person = new Person { FirstName=\, LastName=\, Age=32 }; 然后,编译器就会自动地生成合适的属性setter代码,保持跟前面较冗长的代码例子同样的语意。 除了在初始化类时设置简单的属性值外,对象初始化器特性也允许我们设置更复杂的嵌套(nested)属性类型。譬如,假如我们在上面定义的每个Person类型也拥有一个属于Address类型的叫“Address”的属性。我们可以编写下面这样的代码来生成一个新的Person对象,同时设置它的属性,象这样:
Person person = new Person { FirstName = \, LastName = \ Age = 32, Address = new Address { Street = \, City = \, State = \, Zip = 98052 }
}; Bart De Smet对使用Orcas三月份CTP版中的对象初始化器时内部发生的情形也作了精彩的描述,你可以在这里阅读他的精彩相关帖子。
C#和VB语言的新特性:集合初始化器(Collection Initializers)
对象初始化器很棒,它极大地简化了把对象添加到集合的做法。譬如,假如我要把三个人加到一个基于泛型的类型为Person的List集合中去的话,我可以写下面这样的编码:
List
但Orcas版中的C#和VB编译器允许我们更进一步,现在同时支持“集合初始化器(collection initializers)”,这允许我们避免要写多个Add语句,省下更多的键盘操作:
List
作为开发人员,我们现在拥有了简明得多的方式来定义对象,对它们初始化,将它们加入集合内。在运行时,其语意,跟今天的较长的版本的语意,是完全一样的(所以你不必担心行为会改变)。但现在你不需输入那么多字符了,你的代码将既干净又简明。
在不远的将来,我将发表更多的博客贴子来讨论Orcas版中更多的语言上的改进,包括扩展方法(Extension Methods),Lambdas和匿名类。然后,我将对LINQ做深入讨论,示范它是如何利用所有这些特性来提供一个非常优雅的方式来查询和与数据交互的。
新Orcas语言特性:扩展方法
【原文地址】New \ 【原文发表日期】 Tuesday, March 13, 2007 2:27 AM
上个星期,我发表了我准备写的讨论一些新的VB和C#语言特性的系列博客贴子的第一篇,这些新语言特性是将于今年晚些时候发布的Visual Studio和.NET框架Orcas版的一部分。
我的上一个博客贴子讨论了自动属性,对象初始化器和集合初始化器等新特性。如果你还没有读过这个帖子的话,请在这里阅读。今天的贴子讨论一个VB和C#中都具有的,重要得多的新特性:扩展方法 (Extension Methods)。
什么是扩展方法 (Extension Methods)?
扩展方法允许开发人员往一个现有的CLR类型的公开契约(contract)中添加新的方法,而不用生成子类或者重新编译原来的类型。扩展方法有助于把今天动态语言中流行的对duck typing的支持之灵活性,与强类型语言之性能和编译时验证融合起来。
扩展方法促成了好多有用的使用场景,并使在作为Orcas一部分发布的.NET版本中引进的非常强大的LINQ查询框架成为可能。
简单的扩展方法例子:
有没有想过要检查一个字符串变量是否是个合法的电子邮件地址? 在今天,你大概需要通过调用一个单独的类(或许通过一个静态方法)来实现检查该字符串变量是否合法。譬如,象这样:
string email = Request.QueryString[\]; if ( EmailValidator.IsValid(email) ) { } 而使用C#和VB中的新“扩展方法”语言特性的话,我则可以添加一个有用的“IsValidEmailAddress()”方法到string类本身中去,该方法返回当前字符串实例是否是个合法的字符串。然后我可以把我的代码重写一下,使之更加干净,而且更具描述性,象这样:
string email = Request.QueryString[\]; if ( email.IsValidEmailAddress() ) { } 我们是怎么把这个新的IsValidEmailAddress()方法添加到现有的string类里去的呢?我们是通过定义一个静态的类型,带有我们的“IsValidEmailAddress”这个静态的方法来实现的,象下面这样:
public static class ScottGuExtensions { public static bool IsValidEmailAddress(this string s) { Regex regex = new Regex(@\); return regex.IsMatch(s); } } 注意,上面的静态方法在第一个类型是string的参数变量前有个“this”关键词,这告诉编译器,这个特定的扩展方法应该添加到类型为“string”的对象中去。然后在IsValidEmailAddress()方法实现里,我可以访问调用该方法的实际string实例的所有公开属性/方法/事件,取决于它是否是合法电子邮件地址来返回true/false。
在我的代码里把这个特定的扩展方法的实现添加到string实例,我只要使用标准的“using”语句来引入含有该扩展方法的实现的命名空间:
using ScottGuExtensions; 然后编译器就会在任何string上正确地定位IsValidEmailAddress()方法。在公开发行的Orcas三月份的CTP中的C#和VB在Visual Studio代码编辑器里对扩展方法提供了完整的intellisense支持。所以,当我在一个字符串变量上点击“.”关键词时,我的扩展方法现在就会出现在intellisense的下拉框里:
VB和C#编译器也会很自然地给与你对所有扩展方法用法的编译时的检查,这意味着你会得到一个编译时错误,假如你键错或者错用一个扩展方法的话。
[感谢David Hayden,是他在去年的一个老帖子 里第一个示范了我在上面使用的这个IsValidEmailAddress
使用场景。]
扩展方法使用场景续...
利用扩展方法这个新特性来给个别类型添加方法给开发人员开辟了许多有用的扩展性使用场景。但使得扩展方法非常强有力的是,它们不仅能够应用到个别类型上,也能应用到.NET框架中任何基类或接口上。这允许开发人员建立种种可用于整个.NET框架的丰富的可组合的框架层扩展。
譬如,考虑这样一个场景,我想要一个既容易,描述性又强的方式来检查一个对象是否已经包含在一个对象集合或数组里。我可以定义一个简单的.In(集合)扩展方法,我想把它添加到.NET框架中的所有对象上,我可以在C#里这么来实现这个“In()”扩展方法:
注意上面我是如何声明扩展方法的第一个参数的:“this object o”。这表明,这个扩展方法应该适用于继承于基类System.Object的所有类型,这意味著我可以在.NET中的每个对象上使用它。
上面这个“In”方法的实现允许我检查一个指定的对象是否包含在作为方法参数传入的一个IEnumerable序列中。因为所有的.NET集合和数组都实现了IEnumerable接口,现在我拥有了一个有用的,描述性强的方法来检查一个任意的对象是否属于任何.NET集合或数组。
然后我就可以使用这个“In()”方法来看一个特定的字符串是否在一个字符串数组中:
映射到数据库的“Person”类:
然后我可以使用我先前用于对象和XML文件同样的LINQ Where() 扩展方法,从数据库中获取firstname的首字符为“S”的强类型“Person”对象序列:
注意,查询句法与对象和XML场景中的一模一样。
然后我也可以使用与前面一样的 LINQ Average() 和Max() 扩展方法来从数据库里获取平均和最大值,象这样:
要使上面代码例子工作,你自己不需编写任何SQL代码。Orcas中提供的LINQ到SQL对象关系映射器会处理获取,跟踪,和更新映射到你的数据库数据定义和存储过程的对象。你只要使用任何LINQ扩展方法对结果进行过滤和构形即可,LINQ到SQL会执行获取数据所需的SQL代码(注意,上面的 Average和
Max 扩展方法很明显地不会从数据表中返回所有的数据行,它们会使用TSQL的聚合函数来计算数据库中的值,然后只返回一个标量值)。
请观看我一月份制作的一个录像,演示了LINQ到SQL如何显著地改进了Orcas中的数据生产力。录像中,你也可以看到新的LINQ到SQL的所见即所得ORM设计器的实战示范,以及对数据模型编写LINQ代码时代码编辑器提供的完整的 intellisense。
结语
希望上面的帖子给了你一个对扩展方法工作原理的基本理解,以及你能够利用它们来实现的一些酷扩展性方式。跟任何扩展性机制一样,我要告诫你别一开始就滥建新的扩展方法。不能因为你有一个闪亮的新榔头,就意味着世界上所有的东西突然都变成钉子了!
想着手开始尝试扩展方法的话,我建议你先探究一下Orcas中System.Linq命名空间中提供的标准查询操作符。这些操作符提供了对任何数组,集合,XML流,或关系数据库做丰富的查询的支持,可以极大地改进你操作数据时的生产力。我认为你会发现它们极大地减小了你要在你应用中编写的代码量,允许你编写非常干净和描述性强的语句。它们也允许你在你编码中得到查询逻辑自动的intellisense 和编译时检查。 在下几个星期里,我将继续这个关于Orcas中新语言特性的系列,探讨匿名类和类的推断(Type Inference),还会讨论Lambda的细节和其他酷特性。很明显地,我还会地更多地讨论LINQ。
新Orcas语言特性:Lambda表达式
【原文地址】New \ 【原文发表日期】 Sunday, April 08, 2007 4:21 PM
上个月我开始了一个贴子系列,讨论作为Visual Studio和.NET框架Orcas版本一部分发布的一些新的VB和C#语言特性。下面是这个系列的前2篇贴子:
? ?
自动属性,对象初始化器,和集合初始化器 扩展方法
今天的贴子讨论另一个基础性的新语言特性:Lambda表达式。
什么是Lambda表达式?
随VS 2005发布的C#2.0引进了匿名方法的概念,允许在预期代理(delegate)值的地方用“行内(in-line)”代码块(code blocks)来做替代。
Lambda表达式为编写匿名方法提供了更简明的函数式的句法,但结果却在编写LINQ查询表达式时变得极其有用,因为它们提供了一个非常紧凑的而且类安全的方式来编写可以当作参数来传递,在以后作运算的函数。
Lambda表达式的例子: 在我以前的扩展方法博客贴子里,我演示了你如何可以象下面这样声明一个简单的Person类:
然后,我示范了你可以如何使用一些值来生成一个List
上面高亮标记的红色 p => 表达式就是Lambda表达式。在上面的例子里,我用第一个lambda来指定获取特定人时所用的过滤条件,用第二个lambda来指定在计算平均年龄时该用Person对象的哪个值。
详解Lambda表达式
理解Lambda表达式最容易的方法是把它们设想成编写简明的行内方法的方式。譬如,我上面编写的例子可以使用C#2.0的匿名方法来编写,象这样:
上面两个匿名方法都接受一个Person类型的参数。第一个匿名方法返回一个布尔值,表示Person的LastName是否是Guthrie,第二个匿名方法返回一个整数值(返回那个人的年龄)。我们前面使用的lambda表达式的作用是一样的,两个表达式都接受一个Person类型的参数。第一个lambda表达式返回一个布尔值,第二个返回一个整数。
在C#里,一个lambda表达式在句法上是写成一个参数列表,随后是 => 符号,随后是表达式在调用时要运算的表达式或者语句块: params => expression
所以,当我们编写这样的lambda表达式时: p => p.LastName == \
我们是想表示,我们在定义的Lambda接受一个参数p,要运行的代码表达式返回p.LastName的值是否等于“Guthrie”。 我们将参数命名为p是不相干的,我也可以很容易地将其命名为o,x,foo,或者我想要的任何名字。
不象匿名方法要求参数类型是明确地指明的,Lambda表达式允许省略参数类型,而允许它们根据用法来推断出类型。譬如,当我编写 p=>p.LastName == \这个lambda表达式时,编译器推断出p参数属于Person类型,因为当前的Where扩展方法的对象是个范型的List
Lambda参数的类型可以在编译时和被Visual Studio的intellisense引擎推断出来,这意味着在编写lambda时你将获得完全的intellisense 和编译时检查。譬如,注意当我在下面健入 p. 时,Visual Studio Orcas是如何提供intellisense完成的,因为它知道 p 是 Person类型:
注: 假如你要给一个Lambda表达式明确地声明参数的类型的话,你可以在Lambda参数表里的参数名字前声明参数类型,象这样:
针对框架开发人员的高级内容:Lambda表达式树 (Lambda Expression Trees) 从一个框架开发人员(framework developer)的角度来看,使得Lambda表达式特别强有力的事情之一是,它们既可以以基于IL的方法的形式被编译成代码代理(code delegate),或者也可以编译成一个表达式树(expression tree)对象,然后在运行时用来分析,转换或者优化表达式。
能将Lambda表达式编译成一个表达式树对象是个强大无比的机制,将促成许多使用场景,包括使用能提供编译时句法检查和VS intellisense的统一的查询语言来建立支持丰富数据查询的高性能对象映射器(无论是关系数据库,活动目录,还是web服务)之能力。 从Lambda表达式到代码代理 (Code Delegates)
上面的Where扩展方法是个将Lambda表达式编译成代码代理(code delegate)的例子(意即它是编译成IL的,可以以代理的形式调用)。支持象上面那样过滤任何IEnumerable集合的Where()扩展方法可以使用下面这样的扩展方法代码来实现:
上面的Where()扩展方法接受一个 Func
当我们要想针对类似我们的列表集合一样的内存中的数据做运算时,把lambda表达式编译成代码代理是恰如其分的。但考虑一下你想要查询数据库里的数据的情形(下面的代码是使用Orcas中内置的LINQ到SQL对象关系映射器写成的) :
这里,我要从数据库里取出一串强类型的Product对象,我向Where()扩展方法表示,要通过一个Lambda表达式来做过滤。
我绝对不想要看到发生的是,从数据库里取回所有的产品记录,将它们放在一个局部的集合里,然后在内存里对它运行Where()扩展方法来进行过滤。这么做效率极其不高,对大数据库的扩缩性将是极差的。而我希望的是,LINQ到SQL的ORM将我上面的Lambda过滤条件翻译成SQL表达式,然后在远程的数据库里进行过滤性查询。那样的话,我只返回那些符合查询条件的记录,这样的数据库查询效率是非常高的。 框架开发人员可以通过声明他们的Lambda表达式参数是个Expression
注意上面我是怎么把我们在先前用过的同样的 p=>p.LastName == \表达式,但这次将其赋值给一个 Expression
在LINQ到SQL的情形下,它会将这个Lambda过滤语句翻译成标准的关系SQL语句,来对数据库进行操作(从逻辑上来说,一个“SELECT * from Products where UnitPrice < 55”语句)。 IQueryable
为帮助框架开发人员建立可查询的数据提供器,LINQ提供了 IQueryable
想阅读一些关于如何使用 IQueryable
? ? ?
LINQ to Amazon: Part 1, Part 2, Part 3 LINQ to NHibernate: Part 1, Part 2, Part 3 LINQ to LDAP: Part 1, Part 2, Part 3, Part 4
结语
希望上面的贴子内容对如何考虑和使用Lambda表达式提供了基本的理解。当与Orcas中System.Linq命名空间下提供的内置标准查询扩展方法结合使用时,它们提供了一个非常好的方式来对任何类型的数据进行查询和交互,同时还保持了对完整的编译时检查和intellisense的支持。
通过利用由Lambda提供的对表达式树的支持,以及 IQueryable
在下几个星期里,我将完成这个从理论的层次上讨论新核心语言概念的语言系列,然后转到讨论一些极其实用的实战例子(特别是针对数据库和XML文件使用LINQ的场景)。
新Orcas语言特性:查询句法
【原文地址】New \ 【原文发表日期】 Saturday, April 21, 2007 2:12
上个月我开始了一个贴子系列,讨论作为Visual Studio和.NET框架Orcas版本一部分发布的一些新的VB和C#语言特性。下面是该系列的前三篇贴子的链接:
? ? ?
自动属性,对象初始化器,和集合初始化器 扩展方法 Lambda表达式
今天的贴子要讨论另一个基础性的新语言特性:查询句法(Query Syntax)。
什么是查询句法(Query Syntax)?
查询句法是使用标准的LINQ查询运算符来表达查询时一个方便的声明式简化写法。该句法能在代码里表达查询时增进可读性和简洁性,读起来容易,也容易让人写对。Visual Studio 对查询句法提供了完整的intellisense和编译时检查支持。
在底下,C#和VB编译器则把查询句法的表达式翻译成明确的方法调用代码,这样的代码利用了Orcas中的新的扩展方法和Lambda表达式语言特性。
查询句法的例子:
在我以前的语言系列贴子里,我示范了你可以象下面这样声明一个Person类:
然后我们可以使用下面这样的代码,用一些个人信息来生成一个List
来排序(升序):
上面查询句法的表达式在语意上与下面明确使用LINQ扩展方法和Lambda表达式的代码是等同的:
使用查询句法方法的好处是,结果会是稍微容易读写些,这在表达式变得更繁复时尤其如此。
查询句法 - 理解from和select子句:
在C#中,每个查询表达式的句法从from子句开始,以select或group子句结束。from子句表示你要查询什么数据。select子句则表示你要返回什么数据,且应该以什么构形返回。 譬如,让我们再来看一下我们对List
在上面的代码片段里,\表示了我要对\这个集合做一个LINQ查询,我将用参数\代表我正查询的输入序列的每个项。我们将参数命名为\这个事实是无关紧要的,我完全可以很容易地将其命名为\或我想要的任何名字。
在上面的代码片段里,语句结尾的\p\子句表示,作为查询的结果,我要返回一个Person对象的IEnumerable序列。这是因为\集合包含了Person类型的对象,而参数p则代表了输入序列中的Person对象。因此,该查询句法表达式的结果数据类型是IEnumerable
注意上面我不再说\p\,而是说\p.FirstName\。这表示我不想返回一串Person对象,而是想返回一串字符串,由Person对象的FirstName属性(该属性是个字符串)填充而来。 因此,该查询句法表达式的结果类型是 IEnumerable
LINQ的妙处在于,我可以针对任何数据类型使用完全一样的查询句法。譬如,我可以使用Orcas提供的新LINQ到SQL对象关系映射器支持,对SQL服务器的Northwind数据库进行建模,生成下面这些类(请观看我这里的录像来学习该如何实现):
在上面定义好类模型之后(以及它与数据库间的映射关系),然后我就可以写个查询句法的表达式取出那些单价大于99元的产品:
在上面的代码片段里,我表示我要对NorthwindDataContext类的Products表进行一个LINQ查询,NorthwindDataContext类是由Visual Studio Orcas的ORM设计器生成的。\表示我要返回匹配我的查询的一串Product对象,因此,该查询句法表达式的结果数据类型是IEnumerable
查询句法 - 理解where和orderby子句:
在一个查询句法表达式开头的\子句和结尾的\子句之间,你可以使用最常见的LINQ查询运算符来过滤和转换你在查询的数据。两个最常用的子句是\和\。这两个子句处理对结果集的过
滤和排序。
譬如,要从Northwind数据库里返回按字母降序排列的分类名称列表,过滤条件是只包括那些含有5个以上产品的分类,我们可以编写下面这样的查询句法来用LINQ到SQL对我们的数据库做查询:
在上面的表达式里,我们加了 \子句来表示我们只要那些含有5个以上产品的分类。这利用了数据库中产品和分类间的LINQ到SQL的ORM映射的关联。在上面的表达式中,我也加了\子句来表示我要将结果集按名称降序排列。 LINQ到SQL然后就会在使用这个表达式查询数据库时,生成下列SQL: SELECT [t0].[CategoryName] FROM [dbo].[Categories] AS [t0] WHERE ((
SELECT COUNT(*)
FROM [dbo].[Products] AS [t1]
WHERE [t1].[CategoryID] = [t0].[CategoryID] )) > 5
ORDER BY [t0].[CategoryName] DESC
注意,LINQ到SQL很聪明,只返回了我们所需的单个字段(分类名称), 而且它是在数据库层做了所有的过滤和排序,使得该查询效率非常高。
查询句法 - 用投影(Projection)来转换数据
先前我指出的一个要点是,\子句表示了你要返回的数据,以及这个数据的构形是什么。
譬如,假如你有个象下面这样的\子句,这里p的类型是Person,然后,它就会返回一串Person对象:
LINQ和查询句法提供的一个非常强大的功能是允许你定义跟被查询的数据分开的新的类型,然后用新的类型来控制查询返回的数据的形状和结构。
譬如,假设我们定义了一个新的AlternatePerson类,内含一个FullName属性,而不是我们原先的Person类内的分开的FirstName和LastName属性:
然后我就可以使用下面的LINQ查询句法来查询我原先的List
注意看,我们是如何在上面的表达式里的\子句里,使用我的语言系列的第一个贴子里讨论过的新的对象初始化器句法来创建新的AlternatePerson实例,同时设置它的属性的。也注意我是如何连接我们原先Person类的FirstName和LastName属性,然后将其赋值给FullName属性的。 对数据库使用查询句法投影
这个投影特性在操作从象数据库这样一个远程数据提供器那里取回的数据时,会变得难以置信地有用,因为它提供给我们一个优雅的方式,来表示我们的ORM应该从数据库实际取回哪些数据字段。 譬如,假设我用了LINQ到SQL的ORM提供器对Northwind数据库建模,生成下面这些类:
通过编写下面这个LINQ查询,我告诉LINQ到SQL我要返回一串Product对象:
填充Product类所需的所有字段都将作为上面查询的一部分从数据库中返回,由LINQ到SQL ORM执行的raw SQL看上去象下面这样:
SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued] FROM [dbo].[Products] AS [t0] WHERE [t0].[UnitPrice] > 99
在一些场景下,我不需要也不用所有这些字段,我可以定义一个下面这样的新的MyProduct类,只拥有Product类具有的部分属性,以及一个Product类并不具有的额外属性,TotalRevenue (注: 对那些不熟悉C#的,Decimal?句法表示我们的UnitPrice属性是个nullable值):
然后我就可以使用下面这个查询,使用查询句法的投影功能来构造我要从数据库返回的数据的形状:
这表明,不是返回一串Product对象,我要MyProduct对象,我只要其中三个属性被赋值,LINQ到SQL就会很聪明地调整要执行的raw SQL语句,从数据库只返回那三个需要的产品字段: SELECT [t0].[ProductID], [t0].[ProductName], [t0].[UnitPrice] FROM [dbo].[Products] AS [t0] WHERE [t0].[UnitPrice] > 99
为炫耀起见,我也可以填充MyProduct类的第四个属性,即TotalRevenue属性。我要这个值等于我们产品目前的销售额的总量。这个值在Northwind数据库中并没有作为一个预先算好的字段而存在。而是,你需要在Products表和Order Details表间做一个关联,然后计算出一个给定产品对应的所有的Order Detail 行的总量。
非常酷的是,我可以在Product类的OrderDetails关联上使用LINQ的 Sum 这个扩展方法,编写一个作为我的查询句法投影一部分的乘法Lambda表达式,来计算这个值:
LINQ到SQL就会非常聪明地使用下面这个SQL在SQL数据库里做运算: SELECT [t0].[ProductID], [t0].[ProductName], [t0].[UnitPrice], ( SELECT SUM([t2].[value])
的类定义声明。虽然它们可以在很多场合下使用,但在使用LINQ查询和转换/构形数据时尤其有用。 这个贴子结束了我5个部分的Orcas语言系列。以后,我会发表更多的LINQ贴子,来示范如何在实战上使用所有这些新语言特性来做常见的数据访问操作(定义数据模型,查询,更新,使用存储过程,验证等等)。但我想先把这个5个部分的语言系列完成,这样我们在我将来的贴子里深入探讨时,你才会真正理解所用的底层语言构造。
百度搜索“77cn”或“免费范文网”即可找到本站免费阅读全部范文。收藏本站方便下次阅读,免费范文网,提供经典小说综合文库Linq在线全文阅读。
相关推荐: