我正在从linq表达式生成一个十进制值列表,我想要最小的非零值。但是,linq表达式完全有可能产生一个空列表。
这将引发异常,并且没有MinOrDefault可以应对这种情况。
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).Min();
如果列表为空,将结果设置为0的最佳方法是什么?
答案 0 :(得分:117)
你想要的是这个:
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();
现在,这很整洁!
答案 1 :(得分:51)
decimal? result = (from Item itm in itemList
where itm.Amount != 0
select (decimal?)itm.Amount).Min();
请注意转换为decimal?
。如果没有,你会得到一个空的结果(只是在事后处理 - 我主要说明如何停止异常)。我还使用“非零”使用!=
而不是>
。
答案 2 :(得分:9)
正如已经提到的那样,只需在少量代码中执行一次就是最好的:
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格拉维尔的答案利用了)。同样,如果调用任何其他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();
}
依据long
,float
,double
和decimal
来匹配Min()
提供的Enumerable
集。这就是T4模板很有用的东西。
最后,对于各种类型,我们可以提供MinOrDefault()
的实现,正如我们所希望的那样。当然不是&#34;整洁&#34;面对一次使用(再次,只使用DefaultIfEmpty().Min()
),但非常&#34;整洁&#34;如果我们发现自己经常使用它,那么我们可以重用一个很好的库(或者实际上,粘贴到StackOverflow上的答案......)。
答案 3 :(得分:0)
此方法将返回Amount
中最小的itemList
个值。从理论上讲,应该避免多次往返数据库。
decimal? result = (from Item itm in itemList
where itm.Amount > 0)
.Min(itm => (decimal?)itm.Amount);
由于我们使用的是可空类型,因此不再引起空引用异常。
在调用Any
之前避免使用Min
之类的执行方法,我们应该只进行一次数据库访问
答案 4 :(得分:0)
decimal result;
try{
result = (from Item itm in itemList
where itm.Amount != 0
select (decimal?)itm.Amount).Min();
}catch(Exception e){
result = 0;
}
答案 5 :(得分:-1)
如果itemList是不可为空的(其中DefaultIfEmpty为0),并且您希望将null作为潜在的输出值,则也可以使用lambda语法:
decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);