在LINQ中获取最大值的最佳方法是什么?

3

我是LINQ的新手。我想知道“Date”的最高值,哪种方法更好?

var ma1x= spResult.Where(p =>p.InstrumentId== instrument).OrderByDescending(u => int.Parse(u.Date)).FirstOrDefault();
var max2= spResult.Where(p =>p.InstrumentId== instrument).Max(u => int.Parse(u.Date));

使用Max还是OrderByDescending?


如果 spResult.Date 属性是 DateTime 类型,int.Parse() 应该会失败,因为你不能将 DateTime 转换为 Int32。但是,如果需要,你可以按 DateTime.Ticks 进行排序,或者只按属性本身进行排序 - 无需转换。 - Marco
LINQ扩展.Max<int>在源为空时会抛出异常,但是有一些替代方案可以解决这个问题。 - Slai
4个回答

3

对于开发人员和计算机来说,Max都更好。

Max总是更好的,因为Max是语义化且有意义的。

Enumerable.Max方法

返回值序列中的最大值。

msdn

你想要最大值吗?使用Max。你想要排序吗?使用OrderBy。下一个开发者会感谢你。引用Martin Fowler的话:

任何傻瓜都能写出计算机能理解的代码。好的程序员编写人类可以理解的代码。

如果你真的想使用OrderBy来执行Max的作用,至少将orderby和first封装在一个具有有意义名称的方法中。像... Max这样。太棒了,现在你拥有了一个有意义的OrderBy。

让我们看看这个自定义的Max如何表现。

Enumerable.Max在最坏情况下应该是O(n),当OrderBy使用O(n^2)的快速排序时。因此,自定义的max比标准的更差...

享受性能奖励并选择Enumerable.Max。这对开发人员和计算机都更好。

编辑:

查看Marco的答案以了解它们在实践中的表现。一场赛马比赛总是一个了解哪个更快的好主意。


1

.Max() 应该更快。首先,这个方法的语义更清晰,你的同事会知道你的调用是做什么的。

我在 AdventureWorks2014 数据库上比较了你的两个选项,并使用 LinqPad 进行了以下调用:

var times = new List<long>();

for(var i = 0; i < 1000; i++) {
    Stopwatch sw = Stopwatch.StartNew();
    var max2= SalesOrderHeaders.Max(u => u.OrderDate);
    long elapsed = sw.ElapsedMilliseconds;
    times.Add(elapsed);
}
var averageElapsed = times.Sum (t => t) / times.Count();
averageElapsed.Dump(" ms");

生成的SQL:

SELECT MAX([t0].[OrderDate]) AS [value]
FROM [Sales].[SalesOrderHeader] AS [t0]
GO

结果:

5毫秒

var times = new List<long>();
for(var i = 0; i < 1000; i++) {
    Stopwatch sw = Stopwatch.StartNew();
    var max1 = SalesOrderHeaders.OrderByDescending(u => u.OrderDate).FirstOrDefault();
    long elapsed = sw.ElapsedMilliseconds;
    times.Add(elapsed);
}
var averageElapsed = times.Sum (t => t) / times.Count();
averageElapsed.Dump(" ms");

生成的SQL:

SELECT TOP (1) [t0].[SalesOrderID], [t0].[RevisionNumber], [t0].[OrderDate], [t0].[DueDate], [t0].[ShipDate], [t0].[Status], [t0].[OnlineOrderFlag], [t0].[SalesOrderNumber], [t0].[PurchaseOrderNumber], [t0].[AccountNumber], [t0].[CustomerID], [t0].[SalesPersonID], [t0].[TerritoryID], [t0].[BillToAddressID], [t0].[ShipToAddressID], [t0].[ShipMethodID], [t0].[CreditCardID], [t0].[CreditCardApprovalCode], [t0].[CurrencyRateID], [t0].[SubTotal], [t0].[TaxAmt], [t0].[Freight], [t0].[TotalDue], [t0].[Comment], [t0].[rowguid] AS [Rowguid], [t0].[ModifiedDate]
FROM [Sales].[SalesOrderHeader] AS [t0]
ORDER BY [t0].[OrderDate] DESC
GO

结果:

28毫秒

结论: Max() 更简洁更快!


好久不见的“比赛你的马” :)(关于性能的Eric Lippert) - aloisdg
这并不能给你正确的答案,因为他排序时使用的值似乎被存储为字符串...好吧,这是我能想到他使用int.parse的唯一原因。 - Monofuse
@Monofuse 如果 Date 是一个 DateTime 对象,那么 int.Parse() 甚至无法编译。 - Marco

0

Max 方法比 FirstOrDefault 更好,它们都返回 true 结果,但 Max 的性能更佳。

这段代码:

var ma1x= spResult.Where(p =>p.InstrumentId== instrument).OrderByDescending(u => int.Parse(u.Date)).FirstOrDefault();

首先检查您的条件,然后按照您的条件对它们进行排序,之后将选择并采取更多操作以找到您的结果。


0

纯属猜测,但我想最大值应该是max2。它只是循环遍历每个项并检查其值是否比上一个更高。 而max1则是在检查哪个更高并重新排序。即使只是移动指针(而不是移动值),这仍然需要更多的工作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接