在Linq中实现“MinOrDefault”的最好方法是什么?

我从linqexpression式产生一个十进制值列表,我想要最小的非零值。 然而linqexpression式完全可能会导致一个空的列表。

这将引发一个例外,并且没有MinOrDefault来应付这种情况。

decimal result = (from Item itm in itemList where itm.Amount > 0 select itm.Amount).Min(); 

如果列表为空,那么将结果设置为0的最好方法是什么?

 decimal? result = (from Item itm in itemList where itm.Amount != 0 select (decimal?)itm.Amount).Min(); 

注意到decimal?的转换decimal? 。 如果没有,你会得到一个空的结果(只是在事实后处理 – 我主要是说明如何停止exception)。 我也使“非零”使用!=而不是>

你想要的是这样的:

 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中的function会产生相同的结果(以稍微不同的方式):

 double result = results.DefaultIfEmpty().Min(); 

如果results序列不包含元素,则DefaultIfEmpty()将生成一个包含一个元素( default(T)的序列,随后您可以调用Min()

如果default(T)不是你想要的,那么你可以指定你自己的默认值:

 double myDefault = ... double result = results.DefaultIfEmpty(myDefault).Min(); 

现在,这很整洁!

正如已经提到的那样,在一小段代码中做一次最好的事情就是:

 decimal result = (from Item itm in itemList where itm.Amount > 0 select itm.Amount).DefaultIfEmpty().Min(); 

铸造itm.Amountdecimal? 如果我们希望能够检测到这个空的条件,就获得那个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); } 

无论是否包含select器,以及是否指定缺省值,现在都有一整套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这里我不用await ;我们可以直接创build一个Task<TSource> ,在没有它的情况下完成我们所需要的Task<TSource> ,从而避免了隐藏的复杂问题。

但是等等,还有更多! 比方说,我们使用IEnumerable<T>有些时候。 我们的方法是次优的。 当然,我们可以做得更好!

首先,在int?定义了Min int?long?float? double?decimal? 已经做了我们想要的(正如Marc Gravell的回答所使用的)。 同样,如果调用任何其他T? ,我们也得到了Min所定义的行为T? 。 所以让我们做一些小的,因此容易内联的方法来利用这个事实:

 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(); } 

依此类推, floatdoubledecimalEnumerable提供的Min()集合相匹配。 T4模板是有用的。

最后,我们只需要执行MinOrDefault()就可以满足我们所希望的范围广泛的types。 当然,如果我们发现自己使用它很多,那么在使用它的时候肯定不是“整洁的”(再次,只是使用DefaultIfEmpty().Min() ),但是非常“整洁”,所以我们有一个很好的库重用(或确实,粘贴到StackOverflow的答案…)。

这种方法将从itemList返回一个最小的Amount值。 理论上这应该避免多次往返数据库。

 decimal? result = (from Item itm in itemList where itm.Amount > 0) .Min(itm => (decimal?)itm.Amount); 

空引用exception不再是由于我们使用可空types而引起的。

在调用Min之前,通过避免使用诸如Any之类的执行方法,我们应该只做一次数据库访问