假设我们有一个Foo
类,其中Int32
字段为Bar
,您希望按Foo
值对Bar
个对象的集合进行排序。一种方法是实现IComparable
的{{1}}方法,但也可以使用像这样的语言集成查询(LINQ)来完成
CompareTo()
现在List<Foo> foos = new List<Foo>();
// assign some values here
var sortedFoos = foos.OrderBy(f => f.Bar);
我们收集了sortedFoos
个已整理的集合。但是如果你使用foos
对象来测量System.Diagnostics.StopWatch
对集合进行排序所花费的时间总是0毫秒。但是无论何时打印OrderBy()
集合,它显然都是排序的。怎么可能。文档没有时间来排序集合,但在方法执行后,集合是否已排序?有人可以向我解释一下这是如何起作用的吗?还有一件事。假设在排序sortedFoos
集合后,我添加了另一个元素。现在每当我打印出集合时,我添加的元素应该在最后吗?错!对foos
集合进行排序,即使我在排序后将该元素添加到foos
,我添加的元素也是foos
集合的一部分。我不会放弃了解任何这样的工作,所以任何人都可以为我说清楚吗?!
答案 0 :(得分:5)
几乎所有LINQ方法都使用延迟评估 - 它们不会立即执行任何操作,但是当您请求数据时,它们会设置查询以执行正确的操作。
OrderBy
也遵循此模型 - 尽管它比Where
和Select
等方法更不懒惰。当您从OrderBy
的结果中请求第一个结果时,它将从源中读取所有数据,排序所有它,然后返回第一个元素。例如,将此与Select
进行比较,其中要求投影中的第一个元素仅询问来自源的第一个元素。
如果您对LINQ to Objects如何在幕后工作感兴趣,您可能需要阅读我的Edulinq blog series - LINQ to Objects的完整重新实现,其中有博客文章描述了每个方法的行为和实现。< / p>
(在我自己的OrderBy
实现中,我实际上只是懒惰地排序 - 我使用快速排序并排序“足够”以返回下一个元素。这可以使largeCollection.OrderBy(...).First()
之类的东西很多更快。)
答案 1 :(得分:3)
LINQ相信延迟执行。这意味着只有在开始迭代或访问结果时才会计算表达式。
答案 2 :(得分:1)
OrderBy
扩展使用deault IComparer
作为其正在处理的类型,除非通过适当的重载传递替代。
在首次访问语句返回的IOrderedEnumerable<T>
之前,需要进行排序工作。如果您将Stopwatch
放在第一次访问的周围,您将看到排序需要多长时间。
这很有道理,因为你的语句可能是由多个返回IOrderedEnumerable
的调用组成的。由于排序调用是链接的,它们可以流畅地扩展结果,允许最终返回的IOrderedEnumerable
以最便捷的方式执行排序。这可能是通过链接所有IComparer
次调用和排序一次来实现的。贪婪的实现必须多次浪费排序。
例如,考虑
class MadeUp
{
public int A;
public DateTime B;
public string C;
public Guid D;
}
var verySorted = madeUps.OrderBy(m => m.A)
.ThenBy(m => m.B)
.ThenByDescending(m => m.C)
.ThenBy(m => m.D);
如果贪婪地评估verySorted
,那么将评估序列中的每个属性,并且序列将被重新排序4次。因为IOrderedEnumerable
的linq实现会在枚举之前进行排序,所以它能够优化该过程。
IComparer
,A
,B
和C
的{{1}}可以组合成一个复合委托,类似于此简化表示,
D
然后使用复合委托对序列进行一次排序。
答案 3 :(得分:0)
您必须添加ToList()才能获得新的集合。如果你没有在开始迭代sortedFoos时调用OrderBy。
List<Foo> foos = new List<Foo>();
// assign some values here
var sortedFoos = foos.OrderBy(f => f.Bar).ToList();