在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.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); }
无论是否包含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(); }
依此类推, float
, double
和decimal
与Enumerable
提供的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
之类的执行方法,我们应该只做一次数据库访问