我已经模拟了你的情况,并且这些查询的执行时间确实存在差异。但是,造成这种差异的原因并不是查询的语法。无论您使用方法还是查询语法,都会产生相同的结果,因为查询表达式在编译之前会被转换为其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
的
实现方面进行了一些加速。之后,我们调用
Count()
方法,该方法不需要谓词作为输入,只会迭代过滤后的集合。
关于加速实现WhereListIterator
的问题:
我在SO上发现了this问题:LINQ性能Count vs Where and Count。你可以在那里阅读@Matthew Watson的答案。他解释了这两个查询之间的性能差异。结果是:
Where
迭代器避免了间接虚拟表调用,而直接调用迭代器方法。
正如你在那个答案中看到的,将会发出call
指令而不是callvirt
。而callvirt
比call
慢:
来自书籍CLR via C#
:
使用callvirt IL指令调用虚拟实例方法时,CLR会发现用于调用的对象的实际类型,然后通过多态方式调用该方法。为了确定类型,用于调用的变量不能为null。换句话说,在编译此调用时,JIT编译器生成的代码会验证变量的值是否为null。如果为null,则callvirt指令会导致CLR抛出NullReferenceException异常。
这个额外的检查意味着callvirt IL指令的执行速度比call指令略慢。
pTasks
下面是什么源?这是SQL数据库,还是仅仅是Linq-to-objects? - Jeppe Stig Nielsen