转载

Linq推迟执行

Linq,可能大家都会用,大牛们也会觉得很小儿科,对于初来乍到的新手来说Linq确是一个程序员走上不归路的必修课,不论是使用标准查询运算符(Where(),SelectMany(),Any(),Select(),FirstOrDefault()等等),还是查询查询表达式(from p in list where... select...),都会让我们在编写代码时省力不少。

但是,在使用Linq时,应该注意一个重要概念就是 推迟执行

首先来看一个demo:

int[] ages = new[] { 18, 22, 15, 24, 33 }; var list = ages.Where(p =>  {   var b = p > 20;   if (b)   {    Console.WriteLine("/t" + p);   }   return b;  }); Console.WriteLine("1、年龄大于20的有:"); foreach (var obj in list) { } Console.WriteLine("2、大于20的个数是:"); Console.WriteLine("   {0}个", list.Count()); 

输出结果:

Linq推迟执行

值得注意的是,这里[Console.WriteLine("1、年龄大于20的有:");]是先于Lambda表达式执行的,原因就是Lambda表达式是传递到别的地方的委托,是一个对方法的引用,

foreach循环内会触发Lambda表达式的执行,循环被分解成一个MoveNext()调用,每次调用都会造成原始集合中的每一项都执行一遍Lambda表达式,在循环迭代时每一项都会被运行时判断是否满足表达式的条件。而隐式定义的list接受了Where返回的类型,即IEnumerable<TSource>,而Count()又是Enumerable的函数,所以调用Count()会再一次为每一项触发Lambda表达式。这下就可以理解为什么Lambda不是先输出的了。(这里注意Count()函数和Count属性的区别)。

再来看一个类似的demo:(你怎么那么多demo,F...)

string[] arr = new[] { "赵***", "钱***", "", "", "", "吴***", "", "" }; int counter = 0; Func<string, string> func = p =>     {         counter++;         return p;     }; IEnumerable<string> list = from str in arr  where !str.Contains("*")  select func(str); Console.WriteLine("1、counter = {0}", counter); Console.WriteLine("2、expression list = {0}", list.Count()); Console.WriteLine("3、counter = {0}", counter); Console.WriteLine("4、expression list = {0}", list.Count()); Console.WriteLine("5、counter = {0}", counter); List<string> newlist = list.ToList(); Console.WriteLine("6、after ToList counter = {0}", counter); Console.WriteLine("7、newlist = {0}", newlist.Count()); Console.WriteLine("8、counter = {0}", counter); 

来看下输出结果:

Linq推迟执行

回到上面的概念上func是一个委托,所以list在被赋值时是不会调用委托里的实现的。

这里的查询表达式只是存储了一个选择的条件,以后在遍历由list对象标识的集合时,这些条件才会被调用。

调用ToXXX方法同样会为每一项触发Lambda表达式,但是这些TOXXX方法被调用之后就变成了一个经过标准查询运算符处理过的一个集合。也就是说把表达式查询出来一个结果在内存中存储起来了,这样我们就可以安全的操作集合了。(TOXXX方法会创建‘快照’,当重复调用TOXXX方法时,不会返回新结果,而是快照里的数据)

类似Count()的这种聚合函数还有很多,比如Average(),Sum(),Max(),Min()等,

不光是聚合函数,还有另外一些简单的查询运算符,比如Union(),Concat(),Reverse()...

上述的这些都会触发推迟执行,那么问题来了, 当你的委托里的执行成本比较高时(比如通过网络来调用数据库),就应该避免因使用Count()或foreach而不经意的对集合进行操作。

有不对处,望指正!

参考资料:c#本质论

正文到此结束
Loading...