我正在从 LINQ 表达式生成十进制值列表,并且我想要最小的非零值。但是,LINQ 表达式完全有可能导致一个空列表。
这将引发异常,并且没有 MinOrDefault 来应对这种情况。
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).Min();
如果列表为空,如何将结果设置为 0?
你想要的是这样的:
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()
将生成一个包含一个元素的序列 - the 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 (decimal?)itm.Amount).Min();
注意转换为decimal?
. 如果没有结果,您将得到一个空结果(事后处理 - 我主要说明如何停止异常)。我还做了“非零”使用!=
,而不是>
.
正如已经提到的,在少量代码中只做一次最简洁的是:
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>
有时会使用它。我们的方法是次优的。我们当然可以做得更好!
首先,Min
定义在int?
, long?
,float?
double?
并且decimal?
已经做了我们想要的(正如 Marc Gravell 的回答所使用的那样)。Min
同样,我们也可以从已经定义的 if 中获取我们想要的行为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();
}
依此类推long
,float
,double
和decimal
匹配由Min()
提供的集合Enumerable
。这就是 T4 模板有用的地方。
MinOrDefault()
最后,对于各种类型,我们几乎可以实现我们所希望的性能。面对它的一次使用,当然不是“整洁”(再次,只是 use DefaultIfEmpty().Min()
),但如果我们发现自己经常使用它,就会非常“整洁”,所以我们有一个很好的库可以重用(或者实际上,粘贴到StackOverflow 上的答案……)。
这种方法将返回 中的单个Amount
最小值itemList
。从理论上讲,这应该避免多次往返数据库。
decimal? result = (from Item itm in itemList
where itm.Amount > 0)
.Min(itm => (decimal?)itm.Amount);
由于我们使用的是可空类型,因此不再导致空引用异常。
Any
通过避免使用调用前等执行方法Min
,我们应该只访问一次数据库。
decimal result;
try{
result = (from Item itm in itemList
where itm.Amount != 0
select (decimal?)itm.Amount).Min();
}catch(Exception e){
result = 0;
}
如果 itemList 不可为空(其中 DefaultIfEmpty 给出 0)并且您希望 null 作为潜在的输出值,您也可以使用 lambda 语法:
decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);