方法1:
Product product12 = products.Where(p => p.ProductID == 12).First();
方案2:
Product prod12 = products.First(p => p.ProductID == 12);
Product product12 = products.Where(p => p.ProductID == 12).First();
方案2:
Product prod12 = products.First(p => p.ProductID == 12);
(我假设你正在使用Linq to .Net)
首先让我们看一下它们的源代码:
这里是Where()
:
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);
}
这里是First()
public static TSource First<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate) {
if (source == null) throw Error.ArgumentNull("source");
if (predicate == null) throw Error.ArgumentNull("predicate");
foreach (TSource element in source) {
if (predicate(element)) return element;
}
throw Error.NoMatch();
}
让我们找出每个代码的作用:
products.First(p => p.ProductID == 12);
根据 First()
的源代码,我们可以说,First()
会遍历集合,并在找到满足条件的第一项时停止遍历。
products.Where(p => p.ProductID == 12).First();
首先它将在 Where
方法中创建迭代器,其中元素符合条件。然后,它将再次获取该迭代器的第一个元素。并且,只要找到第一个元素就返回它。
作为附加说明,LINQ 在某些方法中使用延迟执行。它与你的问题结果有一定关系。
延迟执行是一种执行模式,CLR通过该模式确保仅在需要时从基于IEnumerable的信息源中提取值。当任何Linq运算符使用延迟执行时,CLR会将相关信息(例如原始序列、谓词或选择器(如果有))封装到一个迭代器中,在使用ToList方法或ForEach方法或手动使用C#中的底层GetEnumerator和MoveNext方法从原始序列中提取信息时使用该迭代器。问题是:哪一个更快?
Where().First
比 First()
更快。否则,First()
将更快。请参阅@Akash Kava的答案中的详细解释。Where()
的实现。如果你的集合是List,它将返回WhereListIterator()
,但First()
将直接迭代源代码。在我的看法中,他们在WhereListIterator
的实现中进行了一些加速。之后,我们调用First()
方法,该方法不需要谓词作为输入,只会对过滤后的集合进行迭代。Where
迭代器避免了间接的虚表调用,而是直接调用迭代器方法。 这就是加速的原因。x.Size > 20
,那么 Where().First()
将会非常快。
这是 fiddle:https://dotnetfiddle.net/qSLSrI - Farhad Jabiyev<
情况是特殊的,因为结果是集合中的第一个元素。尝试在查询之前添加:items.Reverse();
- Farhad Jabiyev Func<Item,bool> filter = x => x.Size == 200;
Run("First", () => items.First( filter ));
Run("Where First", () => items.Where( filter ).First());
88 milliseconds for First
69 milliseconds for Where First
filter = x => x.Size < 200;
Run("First", () => items.First( filter ));
Run("Where First", () => items.Where( filter ).First());
2 milliseconds for First
4 milliseconds for Where First
filter = x => x.Size > 200;
Run("First", () => items.First( filter ));
Run("Where First", () => items.Where( filter ).First());
88 milliseconds for First
71 milliseconds for Where First
所以没有明确的答案。
另一个测试,https://dotnetfiddle.net/k11nX6,一直以来,First比Where First更快。
更新
在分析List类之后,发现Where().First()只比First()更快,在List和Array的情况下才适用,不适用于IQueryable或任何其他形式的Enumerable。原因是,在List中使用的Enumerator未缓存每个线程。List总是创建新的Enumerator,因此First()使用List的Enumerator(迭代器)。
Where()中使用的WhereListIterator用于List,不会创建新的Enumerator,而是将当前Iterator缓存在线程中。这使得Where().First()运行更快。
然而,缓存WhereListIterator的操作具有一定的成本,因此对于较小的集合和产生较小结果的某些条件,Where().First()会更慢。
如果您查看此示例,则First每次都会击败Where First。
filter = x => x.Size < n;
时,First
只会更快。尝试添加 items.Reverse()
。 - Farhad JabiyevWhere().First()
会更快。这是因为 Where 迭代器避免了间接的虚表调用,而是直接调用迭代器方法。
当然,我们也可以找到 First()
更快的情况。但是,如果有人有时间并且能够搜索并告诉我们背后具体的原因,我会非常高兴。 - Farhad Jabiyev它们在功能上是等效的,同样有效。LINQ构建一个查询,直到结果被枚举(例如foreach循环或在这种情况下的First()操作)才进行评估。
因此,两者都将按原始顺序评估项目,直到找到第一个ProductID == 12的实例,并返回该实例(如果找到则抛出异常)。
话虽如此,后一种方法是我更喜欢的方法,因为它更简洁,通常更易于阅读。
我认为你的问题更像是:
Where
在到达 First
之前会遍历整个集合吗?First
也会这样做吗?我编写了自己的 Where
和 First
实现。
这个例子不是精确的 .net 实现,但它在相同的概念下工作。
public static class Extensions
{
public static IEnumerable<T> MyWhere<T>(this IEnumerable<T> collection, Func<T,bool> condition)
{
foreach(var item in collection)
{
if(condition(item))
{
yield return item;
}
}
}
public static T MyFirst<T>(this IEnumerable<T> collection, Func<T,bool> condition)
{
foreach (var item in collection)
{
if (condition(item))
{
return item;
}
}
throw new InvalidOperationException("No element found");
}
}
List<int> myList = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var five = myList.MyWhere(i => i < 6).MyFirst(i => i == 5);
在MyWhere
和MyFirst
的foreach
内部放置一个调试器断点,并开始理解使用Linq To Objects迭代器时发生的情况。