如何在LINQ中实现“MinOrDefault”?

oxcyiej7  于 2022-12-06  发布在  其他
关注(0)|答案(6)|浏览(155)

我正在从LINQ表达式生成一个十进制值列表,我想要最小的非零值。但是,LINQ表达式完全有可能生成一个空列表。
这将引发异常,并且没有MinOrDefault来科普这种情况。

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

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

cngwdvgl

cngwdvgl1#

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

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

qij5mzcb2#

decimal result;
try{
  result = (from Item itm in itemList
                  where itm.Amount != 0
                  select (decimal?)itm.Amount).Min();
}catch(Exception e){
  result = 0;
}
liwlm1x9

liwlm1x93#

你想要的是:

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

现在,这是整齐!

jmp7cifd

jmp7cifd4#

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

注意到到decimal?的转换。如果没有,你会得到一个空的结果(只是在事后处理-我主要演示如何停止异常)。我还让“非零”使用!=而不是>

amrnrhlw

amrnrhlw5#

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

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

(Note这里我没有使用await我们可以直接创建一个Task<TSource>,它可以在没有它的情况下完成我们需要的工作,从而避免await带来的隐藏复杂性)。
但是等等,还有更多!假设我们有时在IEnumerable<T>中使用这个方法。我们的方法是次优的。当然我们可以做得更好!
首先,在int?long?float?double?decimal?上定义的Min已经做了我们想要做的事情(正如Marc Gravell的答案所使用的)。类似地,如果为任何其他T?调用,我们也可以从已经定义的Min中获得我们想要的行为。和易于内联的方法来利用这一事实:

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

以此类推,对于longfloatdoubledecimal,匹配Enumerable提供的Min()集合。这是T4模板有用的地方。
最后,我们得到了MinOrDefault()的一个实现,它的性能与我们所希望的差不多,适用于各种类型。(同样,只使用DefaultIfEmpty().Min()),但如果我们发现自己经常使用它,它会非常“整洁”,因此我们有一个可以重用的好库(或者实际上,粘贴到StackOverflow上的答案中...)。

neskvpey

neskvpey6#

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

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

因为我们使用的是可为null的型别,所以不会再造成null指涉例外状况。
通过避免在调用Min之前使用执行方法(如Any),我们应该只访问数据库一次

相关问题