如何在 LINQ 中实现“MinOrDefault”?

问题描述 投票:0回答:6

我正在从 LINQ 表达式生成一个十进制值列表,我想要最小非零值。然而,LINQ 表达式完全有可能导致一个空列表。

这会引发异常,没有 MinOrDefault 来应对这种情况。

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

如果列表为空,如何将结果设置为 0?

c# linq min
6个回答
149
投票

你要的是这个:

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()
on.

如果

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

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

现在,这很整洁!


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

注意转换为

decimal?
。如果没有结果,您将得到一个空结果(只需在事后处理 - 我主要说明如何停止异常)。我也让“非零”使用
!=
而不是
>
.


24
投票

正如已经提到的,在少量代码中只做一次的最简洁的是:

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().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<TResult> MinOrDefaultAsync<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, TSource defaultValue)
{
  return source.DefaultIfEmpty(defaultValue).MinAsync(selector);
}

public static Task<TResult> 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
,我们也可以从已经定义的
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 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()
实现的性能与我们希望的差不多。面对一次使用当然不是“整洁”(同样,只需使用
DefaultIfEmpty().Min()
),但如果我们发现自己经常使用它,则非常“整洁”,所以我们有一个很好的库,我们可以重用(或者实际上,粘贴到 StackOverflow 上的答案中……)。


1
投票

此方法将从

Amount
返回单个最小的
itemList
值。从理论上讲,这应该避免多次往返数据库。

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

不再因为我们使用的是可空类型而导致空引用异常

通过避免在调用

Any
之前使用
Min
等执行方法,我们应该只访问数据库一次


0
投票

如果 itemList 不可为空(其中 DefaultIfEmpty 给出 0)并且您希望 null 作为潜在的输出值,您也可以使用 lambda 语法:

decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);

0
投票
decimal result;
try{
  result = (from Item itm in itemList
                  where itm.Amount != 0
                  select (decimal?)itm.Amount).Min();
}catch(Exception e){
  result = 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.