要理解延迟求值(lazy evaluation)和主动求值(eager evaluation),首先来看一个例子:
List<int>list=new List<int>(){0,1,2,3,4,5,6,7,8,9};
var temp1=from c in list where c>5 select c;
var temp2=(from c in list where c>5 select c).ToList<int>();
list[0]=11;
Console.Write("temp1:");
foreach(var item in temp1)
{
Console.Write(item.ToString()+"");
}
Console.Write("\ntemp2:");
foreach(var item in temp2)
{
Console.Write(item.ToString()+"");
}
上面代码的输出为:
temp1:11 6 7 8 9
temp2:6 7 8 9
在延迟求值的情况下,只是定义了一个查询,而且不是立刻执行。对查询结果的访问每次都会遍历原集合。如上文中对于temp1的迭代,在迭代之前,我们修改了list[0]的值,可以看到,修改直接影响了迭代的输出。对查询调用ToList、ToArray等方法,将会使其立即执行,由于对list[0]的修改是在temp2查询之后进行的,所以针对list[0]的修改不会影响到temp2的结果。
在使用LINQ to SQL时,延迟求值能够带来显著的性能提升。举个例子:如果定义了两个查询,而且采用延迟求值,CLR则会合并两次查询并生成一个最终的查询:
static void Main(string[]args)
{
DataContext ctx=new DataContext("server=192.168.0.102;database=Temp;
uid=sa;pwd=sa123");
Table<Person>persons=ctx.GetTable<Person>();
var temp1=from p in persons where p.Age>20 select p;
//省略
var temp2=from p in temp1 where p.Name.IndexOf('e')>0 select p;
foreach(var item in temp2)
{
Console.WriteLine(string.Format("Name:{0}\tAge:{1}",item.Name,
item.Age));
}
}
[Table(Name="Person")]
class Person
{
[Column]
public string Name{get;set;}
[Column]
public int Age{get;set;}
}
注意 这段代码需要SQL Server数据库的支持。本段代码假设已经存在一个Temp的数据库,其中有一个Person表,表内包含两个字段:
Name,varchar(50)
Age,int
在迭代开始的时候,LINQ to SQL引擎会生成如下的SQL查询语句:
exec sp_executesql N'SELECT[t0].[Name],[t0].[Age]
FROM[Person]AS[t0]
WHERE((
(CASE
WHEN(DATALENGTH(@p0)/2)=0 THEN CONVERT(BigInt,0)
ELSE CONVERT(BigInt,(CONVERT(Int,CHARINDEX(@p0,[t0].[Name])))-1)
END))>@p1)AND([t0].[Age]>@p2)',N'@p0 nchar(1),@p1 int,@p2 int',
@p0=N'e',@p1=0,@p2=20
可以看到,最终的SQL语句中合并了对年龄和姓名的条件查询。如果我们的数据库中存在如下的记录(见表2-3):
表2-3 Person表
那么根据上面的查询条件,数据库将会返回两条记录:
Name:Steve Age:21
Name:Jessica Age:22
如果采用主动求值:
var temp1=(from p in persons where p.Age>20 select p).ToList<Person>();
//省略
var temp2=from p in temp1 where p.Name.IndexOf('e')>0 select p;
会生成下面的语句,可以看到SQL语句仅仅包含对年龄的条件筛选:
exec sp_executesql N'SELECT[t0].[Name],[t0].[Age]
FROM[Person]AS[t0]
WHERE[t0].[Age]>@p0',N'@p0 int',@p0=20
最终,数据库会返回三条语句,如下所示。
Name:Steve Age:21
Name:Jessica Age:22
Name:Lisa Age:23
虽然执行完temp2的查询后返回的结果也是两条记录,但是针对temp2的查询实际是对已经返回到本地的这三条数据进行的筛选。在这个例子中,返回三条或两条语句带来的效率问题似乎并不明显,但是,如果将应用放到一个互联网系统中,在每个地方减少一定的流量,则将会为我们带来客观的性能要求。
事实上,应该仔细体会延迟求值和主动求值之间的区别,体会两者在应用中会带来什么样的输出结果;否则,很有可能会出现一些我们意想不到的Bug。