如何在 LINQ 中实现“MinOrDefault”?
Posted
技术标签:
【中文标题】如何在 LINQ 中实现“MinOrDefault”?【英文标题】:How to achieve "MinOrDefault" in LINQ? 【发布时间】:2011-01-11 01:23:13 【问题描述】:我正在从 LINQ 表达式生成十进制值列表,并且我想要最小的非零值。但是,LINQ 表达式完全有可能导致一个空列表。
这将引发异常,并且没有 MinOrDefault 来应对这种情况。
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).Min();
如果列表为空,如何将结果设置为 0?
【问题讨论】:
【参考方案1】:你想要的是这个:
IEnumerable<double> results = ... your query ...
double result = results.MinOrDefault();
好吧,MinOrDefault()
不存在。但如果我们自己实现它,它看起来像这样:
public static class EnumerableExtensions
public static T MinOrDefault<T>(this IEnumerable<T> sequence)
if (sequence.Any())
return sequence.Min();
else
return default(T);
但是,System.Linq
中的某些功能会产生相同的结果(方式略有不同):
double result = results.DefaultIfEmpty().Min();
如果results
序列不包含任何元素,DefaultIfEmpty()
将生成一个包含一个元素的序列 - default(T)
- 随后您可以调用 Min()
。
如果 default(T)
不是您想要的,那么您可以指定您自己的默认值:
double myDefault = ...
double result = results.DefaultIfEmpty(myDefault).Min();
现在,这很整洁!
【讨论】:
@ChristofferLette 我只想要一个 T 的空列表,所以我最终也使用了带有 Min() 的 Any()。谢谢! @AdrianMar:顺便说一句,你考虑过使用Null Object 作为默认值吗? 这里提到的 MinOrDefault 实现将遍历可枚举两次。内存中的集合无关紧要,但对于 LINQ to Entity 或惰性“yield return”内置枚举,这意味着两次往返数据库或处理第一个元素两次。我更喜欢 results.DefaultIfEmpty(myDefault).Min() 解决方案。 看DefaultIfEmpty
的源码,确实是智能实现的,只有在有元素使用yield return
s时才会转发序列。
@JDandChips 您引用的格式为DefaultIfEmpty
,采用IEnumerable<T>
。如果您在IQueryable<T>
上调用它,就像您在数据库操作中那样,那么它不会返回单例序列,而是生成适当的MethodCallExpression
,因此生成的查询不需要检索所有内容。不过,这里建议的 EnumerableExtensions
方法确实存在这个问题。【参考方案2】:
decimal? result = (from Item itm in itemList
where itm.Amount != 0
select (decimal?)itm.Amount).Min();
注意转换为decimal?
。如果没有结果,您将得到一个空结果(事后处理 - 我主要说明如何停止异常)。我还“非零”使用!=
而不是>
。
【讨论】:
有趣。我不知道这将如何避免空列表,但我会试一试 试试看:decimal? result = (new decimal?[0]).Min();
给了null
也许然后使用 ?? 0 得到想要的结果?
绝对有效。我刚刚建立了一个单元测试来尝试它,但我将不得不花 5 分钟来弄清楚为什么选择的结果是一个空值而不是一个空列表(我的 sql 背景可能让我感到困惑)。谢谢你。
@Lette,如果我将其更改为:decimal result1 = .....Min() ?? 0;这也有效,所以感谢您的意见。【参考方案3】:
如前所述,只需少量代码执行一次,最简洁的方法是:
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).DefaultIfEmpty().Min();
将itm.Amount
转换为decimal?
并获得Min
是最简洁的,如果我们希望能够检测到这种空状态。
如果您想实际提供MinOrDefault()
,那么我们当然可以从以下开始:
public static TSource MinOrDefault<TSource>(this IQueryable<TSource> source, TSource defaultValue)
return source.DefaultIfEmpty(defaultValue).Min();
public static TSource MinOrDefault<TSource>(this IQueryable<TSource> source)
return source.DefaultIfEmpty(defaultValue).Min();
public static TResult MinOrDefault<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, TSource defaultValue)
return source.DefaultIfEmpty(defaultValue).Min(selector);
public static TResult MinOrDefault<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)
return source.DefaultIfEmpty().Min(selector);
您现在拥有一整套MinOrDefault
,无论您是否包含选择器,以及您是否指定默认值。
从这一点上你的代码很简单:
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).MinOrDefault();
所以,虽然一开始并不那么整洁,但从那时起就更整洁了。
但是等等!还有更多!
假设您使用 EF 并希望使用 async
支持。轻松搞定:
public static Task<TSource> MinOrDefaultAsync<TSource>(this IQueryable<TSource> source, TSource defaultValue)
return source.DefaultIfEmpty(defaultValue).MinAsync();
public static Task<TSource> MinOrDefaultAsync<TSource>(this IQueryable<TSource> source)
return source.DefaultIfEmpty(defaultValue).MinAsync();
public static Task<TSource> MinOrDefaultAsync<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, TSource defaultValue)
return source.DefaultIfEmpty(defaultValue).MinAsync(selector);
public static Task<TSource> MinOrDefaultAsync<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)
return source.DefaultIfEmpty().MinAsync(selector);
(请注意,我在这里没有使用await
;我们可以直接创建一个Task<TSource>
,没有它就可以满足我们的需要,从而避免await
带来的隐藏复杂性)。
但是等等,还有更多!假设我们有时将它与IEnumerable<T>
一起使用。我们的方法是次优的。我们当然可以做得更好!
首先,int?
、long?
、float?
double?
和 decimal?
上定义的 Min
无论如何都已经做了我们想要的(正如 Marc Gravell 的回答所使用的那样)。同样,如果调用任何其他T?
,我们也可以从已经定义的Min
中获得我们想要的行为。所以让我们做一些小的,因此很容易内联的方法来利用这个事实:
public static TSource? MinOrDefault<TSource>(this IEnumerable<TSource?> source, TSource? defaultValue) where TSource : struct
return source.Min() ?? defaultValue;
public static TSource? MinOrDefault<TSource>(this IEnumerable<TSource?> source) where TSource : struct
return source.Min();
public static TResult? Min<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult?> selector, TResult? defaultValue) where TResult : struct
return source.Min(selector) ?? defaultValue;
public static TResult? Min<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult?> selector) where TResult : struct
return source.Min(selector);
现在让我们先从更一般的情况开始:
public static TSource MinOrDefault<TSource>(this IEnumerable<TSource> source, TSource defaultValue)
if(default(TSource) == null) //Nullable type. Min already copes with empty sequences
//Note that the jitter generally removes this code completely when `TSource` is not nullable.
var result = source.Min();
return result == null ? defaultValue : result;
else
//Note that the jitter generally removes this code completely when `TSource` is nullable.
var comparer = Comparer<TSource>.Default;
using(var en = source.GetEnumerator())
if(en.MoveNext())
var currentMin = en.Current;
while(en.MoveNext())
var current = en.Current;
if(comparer.Compare(current, currentMin) < 0)
currentMin = current;
return currentMin;
return defaultValue;
现在使用这个的明显覆盖:
public static TSource MinOrDefault<TSource>(this IEnumerable<TSource> source)
var defaultValue = default(TSource);
return defaultValue == null ? source.Min() : source.MinOrDefault(defaultValue);
public static TResult MinOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector, TResult defaultValue)
return source.Select(selector).MinOrDefault(defaultValue);
public static TResult MinOrDefault<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
return source.Select(selector).MinOrDefault();
如果我们真的看好性能,我们可以针对某些情况进行优化,就像Enumerable.Min()
所做的那样:
public static int MinOrDefault(this IEnumerable<int> source, int defaultValue)
using(var en = source.GetEnumerator())
if(en.MoveNext())
var currentMin = en.Current;
while(en.MoveNext())
var current = en.Current;
if(current < currentMin)
currentMin = current;
return currentMin;
return defaultValue;
public static int MinOrDefault(this IEnumerable<int> source)
return source.MinOrDefault(0);
public static int MinOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector, int defaultValue)
return source.Select(selector).MinOrDefault(defaultValue);
public static int MinOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
return source.Select(selector).MinOrDefault();
long
、float
、double
和 decimal
以此类推,以匹配 Enumerable
提供的 Min()
集合。这就是 T4 模板有用的地方。
最后,我们的MinOrDefault()
实现的性能几乎与我们希望的一样,适用于各种类型。面对它的一个用途,当然不是“整洁”(再次,只需使用DefaultIfEmpty().Min()
),但如果我们发现自己经常使用它,那就非常“整洁”,所以我们有一个很好的库可以重用(或者实际上,粘贴到 *** 上的答案中……)。
【讨论】:
【参考方案4】:此方法将从itemList
返回单个最小的Amount
值。理论上,这应该避免多次往返数据库。
decimal? result = (from Item itm in itemList
where itm.Amount > 0)
.Min(itm => (decimal?)itm.Amount);
空引用异常不再因为我们使用的是可空类型而导致。
通过避免在调用Min
之前使用诸如Any
之类的执行方法,我们应该只访问一次数据库
【讨论】:
是什么让您认为在接受的答案中使用Select
会多次执行查询?接受的答案将导致单个数据库调用。
你说得对,Select
是一种延迟方法,不会导致执行。我已经从我的回答中删除了这些谎言。参考:Adam Freeman 的“Pro ASP.NET MVC4”(书籍)
如果您想真正看好确保没有浪费,请查看我刚刚发布的答案。【参考方案5】:
decimal result;
try
result = (from Item itm in itemList
where itm.Amount != 0
select (decimal?)itm.Amount).Min();
catch(Exception e)
result = 0;
【讨论】:
【参考方案6】:如果 itemList 不可为空(DefaultIfEmpty 给出 0)并且您希望 null 作为潜在的输出值,您也可以使用 lambda 语法:
decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);
【讨论】:
以上是关于如何在 LINQ 中实现“MinOrDefault”?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Entity Framework Core 2.0 上的 lambda 语法在 LINQ 中实现 LEFT OUTER JOIN?
有没有啥简单的方法可以在 Linq 中实现 SQL Server 的合并查询?