代码之家  ›  专栏  ›  技术社区  ›  Chris Simpson

在LINQ中实现“minordefault”最简洁的方法是什么?

  •  71
  • Chris Simpson  · 技术社区  · 15 年前

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

    这将引发一个异常,并且不存在处理这种情况的次要错误。

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

    如果列表为空,将结果设置为0的最新方法是什么?

    4 回复  |  直到 6 年前
        1
  •  50
  •   Marc Gravell    15 年前
    decimal? result = (from Item itm in itemList
                      where itm.Amount != 0
                      select (decimal?)itm.Amount).Min();
    

    注意转换为 decimal? . 如果没有结果,您将得到一个空结果(只需在事实之后处理它—我主要演示如何停止异常)。我还用了“非零”字 != 而不是 > .

        2
  •  112
  •   Christoffer Lette    15 年前

    你想要的是:

    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() 在。

    如果 缺省(t) 不是您想要的,那么您可以使用以下命令指定自己的默认值:

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

    现在,这很好!

        3
  •  9
  •   Jon Hanna    10 年前

    如前所述,仅在少量代码中进行一次的neatest是:

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

    所以,虽然开始的时候不那么整洁,但从那时起就更整洁了。

    但是等等!还有更多!

    比如说你用英孚,想利用 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> 这是我们需要的,没有它,因此避免了隐藏的并发症。 等待 带来)。

    但是等等,还有更多!假设我们用这个 IEnumerable<T> 有时。我们的方法是次优的。我们当然可以做得更好!

    首先, 分钟 定义在 int? , long? , float? double? 十进制的? 不管怎样,我们已经做了我们想做的(正如马克·格雷威尔的回答所利用的)。同样,我们也可以从 分钟 已定义,如果调用任何其他 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()号 正如我们所希望的,适用于各种类型。当然,在一次使用中不会“整洁”(同样,只是使用 DefaultIfEmpty().Min() ,但是如果我们发现自己经常使用它,那就非常“整洁”,因此我们有一个很好的库可以重用(或者确实,粘贴到stackoverflow_上的答案中)。

        4
  •  0
  •   JDandChips    10 年前

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

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

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

    通过避免使用诸如 Any 呼叫前 Min ,我们应该只访问一次数据库