Linq中Count()和Any()引发的效率问题

1、count和any

今天看了鹤冲天的文章:Linq:切勿使用 Count() > 0 来判断集合非空   有所收获,写下文章总结一下:

先看如下代码:

 1        static void Main(string[] args)
 2         {
 3             Stopwatch watch = new Stopwatch();
 4             watch.Start();
 5             var nums = GetNums(0, 100000000);
 6             SomeAction(nums);
 7             watch.Stop();
 8             Console.WriteLine(watch.ElapsedMilliseconds);
 9             Console.WriteLine("over");
10             Console.ReadKey();
11         }
12         public static IEnumerable<int> GetNums(int start, int count)
13         {
14             var end = start + count;
15             for (int i = start; i < end; i++)
16                 yield return i;
17         }
18         public static void SomeAction<T>(IEnumerable<T> source)
19         {
20             //if (source.Count() > 0)  //2233 毫秒
21             if (source.Any())          //3 毫秒
22             {
23                 Console.WriteLine("have data");
24             }
25         }

自己调试了下,当yield循环低于100000次的话,count()和Any()的执行时间差不多,count()时间略大于any()所耗费的时间,当大于100000次的话,count()执行时间就远比Any()执行时间多很多!例如上面的程序执行结果,count耗时2233毫秒,any却只耗时3毫秒!这是为什么呢?我们来看下原因:分别反编译 Enumerate.Count 的源码和 Enumerate.Any的源码,如下:

Enumerate.Any 实现代码如下:

 1 public static bool Any<TSource>(this IEnumerable<TSource> source)
 2 {
 3     if (source == null)
 4     {
 5         throw Error.ArgumentNull("source");
 6     }
 7     using (IEnumerator<TSource> enumerator = source.GetEnumerator())
 8     {
 9         if (enumerator.MoveNext())
10         {
11             return true;
12         }
13     }
14     return false;
15 }

 Enumerate.Count 的源码如下:

 1 public static int Count<TSource>(this IEnumerable<TSource> source)
 2 {
 3     if (source == null)
 4     {
 5         throw Error.ArgumentNull("source");
 6     }
 7     ICollection<TSource> is2 = source as ICollection<TSource>;
 8     if (is2 != null)
 9     {
10         return is2.Count;
11     }
12     ICollection is3 = source as ICollection;
13     if (is3 != null)
14     {
15         return is3.Count;
16     }
17     int num = 0;
18     using (IEnumerator<TSource> enumerator = source.GetEnumerator())
19     {
20         while (enumerator.MoveNext())
21         {
22             num++;
23         }
24     }
25     return num;
26 }

无论循环多少次,Any方法执行的时间总是最少的,大概10毫秒之内,我想大家从Any的源码可以知道这个原因,Any方法先判断集合是否为空,然后判断集合是否有数据,只进行一次movenext(),并未进行循环查询集合数量!所以耗费的时间当然一直是最少的了!

我们再看Count方法,同样是先进行集合判空操作,然后判断当前的集合source是否能转成ICollection类型,如果能转成,就直接返回此集合的Count属性,不会在进行下面的循环获取集合个数的操作了,为什么Count()方法比Any()方法执行的时间长呢?答案就在于此:因为GetNums方法中用yield返回纯粹的IEnumerable<int>类型,但是ICollection<T>是继承自IEnumerable<T>类型的,所以必然不好被转换,也就是Count()源码中的下面代码不会执行

     ICollection is3 = source as ICollection;
     if (is3 != null)
     {
         return is3.Count;
     }

上面的代码不会执行,那么必然会进行下面的代码操作,也就是循环获取集合的个数:

     int num = 0;
     using (IEnumerator<TSource> enumerator = source.GetEnumerator())
     {
         while (enumerator.MoveNext())
         {
             num++;
         }
     }
     return num;

这样必然会导致Count()方法耗时很久!

2、如果我们把GetNums进行如下修改,其他代码不动:

       public static IEnumerable<int> GetNums(int start, int count)
        {
            List<int> list = new List<int>();
            var end = start + count;
            for (int i = start; i < end; i++)
                list.Add(i);
            return list;
        }

这时我们在执行程序,可以看到无论循环次数多少,Count和Any两者执行的时间差不多,大概是1315毫秒左右,其中1312毫秒用于list集合的装填工作,3毫秒用于Count或Any的判断时间,为什么会出现这种情况呢,我想通过上面大家都知道了,因为此时GetNums方法返回的是List<T>类型,此类型继承于ICollection,所以必然可以被转换,就会返回此类型的Count属性,Count 方法对 ICollection 进行了优化,直接访返回它的 Count 属性,也就是返回一个数,当然很快了,下面的循环获取集合的个数当然也就不会执行了,也就节约了时间。

3、对某人问题的思考

有人说:”我有个程序里经常要判集合里是否仅有一个元素,又不能用Count,不得已自己写了个扩展方法 bool Check(this IEnumerable<T> source, int n), 当读到第n+1个元素就直接返回false“

我想如果此人用的集合是继承于ICollection<T>的话,例如List<T>,那么是没必要对IEnumerable<T>进行扩展的,通过反编译Count源码可以知道,如果集合source可以转换成ICollection的话,是可以直接通过count属性获取到集合的总数量的,所以耗费的时间要不了多少,也就没必要对IEnumerable进行扩展了!反之,如果不是继承ICollection<T>的话,例如IEnumerable,就必须自己扩展IEnumerable方法了,如果还用count的话,就会造成循环了,也就降低了程序的运行效率了!

 4、参考

   http://www.cnblogs.com/ldp615/archive/2011/12/11/2284154.html#comment_tip  鹤冲天

   

作者:MrZivChu

2013-12-13   21:30:12

原文地址:https://www.cnblogs.com/MrZivChu/p/BaseKnowledge_Count_Any.html