【转】编写高质量代码改善C#程序的157个建议——建议28:理解延迟求值和主动求值之间的区别
建议28:理解延迟求值和主动求值之间的区别
要理解延迟求值(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 + " "); } Console.Write("\ntemp2: "); foreach (var item in temp2) { Console.Write(item + " "); }
输出:
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语句合并了对年龄和姓名条件的查询。
如果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;
会生成下面的语句:
exec sp_executesql N'SELECT [t0].[Name], [to].[Age]
FROM [Person] AS [t0]
WHERE [t0].[Age]>@p0',N'@p0 int',@p0=20
数据库会返回3条数据
Name:Steve Age:21
Name:Jessica Age:22
Name:Lisa Age:23
虽然temp2的查询返回的结果也是两条记录,但是针对temp2的查询实际是对已经返回到本地的3条数据进行的筛选。这个例子中,返回3条或2条带来的效率问题并不明显,但是,将应用放到互联网系统中,每个地方减少一定的流量,则会给我们带来可观的性能提升。
事实上,应该仔细体会延迟求值和主动求值之间的区别,体会两者在应用中会带来什么样的输出结果:否则,很有肯能会出现一些我们意想不到的Bug。
转自:《编写高质量代码改善C#程序的157个建议》陆敏技