我已经模拟了你的情况。是的,这些查询的执行时间存在差异。但是,这种差异的原因不是查询的语法。您是否使用了方法或查询语法并不重要。两者产生相同的结果,因为查询表达式在编译之前被翻译成它们的 lambda 表达式。
但是,如果您注意到这两个查询根本不一样。您的第二个查询将在编译之前转换为它的 lambda 语法(您可以删除 ToList() 来自查询,因为它是多余的):
pTasks.Where(x => x.StatusID == (int)BusinessRule.TaskStatus.Pending).Count();
现在我们有两个 lambda 语法的 Linq 查询。我上面说过的那个和这个:
pTasks.Count(x => x.StatusID == (int)BusinessRule.TaskStatus.Pending);
现在的问题是:
为什么这两个查询的执行时间会有差异?
让我们找到答案:
我们可以通过回顾这些来了解这种差异的原因:
- .Where(this IEnumerable<TSource> source, Func<TSource, bool> predicate).Count(this IEnumerable<TSource> source)
和
-Count(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
这里是Count(this IEnumerable<TSource> source, Func<TSource, bool> predicate)的实现:
public static int Count<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
int count = 0;
foreach (TSource element in source) {
checked {
if (predicate(element)) count++;
}
}
return count;
}
这里是Where(this IEnumerable<TSource> source, Func<TSource, bool> predicate):
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
if (source == null)
throw Error.ArgumentNull("source");
if (predicate == null)
throw Error.ArgumentNull("predicate");
if (source is Iterator<TSource>)
return ((Iterator<TSource>)source).Where(predicate);
if (source is TSource[])
return new WhereArrayIterator<TSource>((TSource[])source, predicate);
if (source is List<TSource>)
return new WhereListIterator<TSource>((List<TSource>)source, predicate);
return new WhereEnumerableIterator<TSource>(source, predicate);
}
让我们关注Where() 的实现。如果您的集合是 List,它将返回 WhereListIterator(),但 Count() 只会遍历源代码。
在我看来,他们在WhereListIterator 的implementation 中取得了一些加速。在此之后,我们调用Count() 方法,该方法不接受谓词作为输入,只会在过滤后的集合上进行迭代。
关于WhereListIterator 的执行速度:
我在 SO:LINQ performance Count vs Where and Count 中找到了this 问题。你可以在那里阅读@Matthew Watson answer。他解释了这两个查询之间的性能差异。结果是:
Where 迭代器避免了间接虚表调用,而是直接调用迭代器方法。
正如您在该答案中看到的那样,将发出call 指令而不是callvirt。而且,callvirt 比 call 慢:
来自bookCLR via C#:
使用callvirt IL指令调用虚拟实例时
方法中,CLR 发现正在使用的对象的实际类型
进行调用,然后以多态方式调用该方法。为了
确定类型,用于进行调用的变量不得
为空。换句话说,当编译这个调用时,JIT 编译器
生成验证变量值不为空的代码。如果
它为空,callvirt 指令导致 CLR 抛出一个
空引用异常。 这个额外的检查意味着 callvirt
IL 指令的执行速度比调用稍慢
说明。