C# 在 Linq 中实现“MinOrDefault”的最简洁方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2165605/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-07 00:05:01  来源:igfitidea点击:

What's the neatest way to achieve "MinOrDefault" in Linq?

c#linq

提问by Chris Simpson

I'm producing a list of decimal values from a linq expression and I want the minimum non zero value. However it's entirely possible that the linq expression will result in an empty list.

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

This will raise an exception and there is no MinOrDefault to cope with this situation.

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

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

What's the neatest way to set the result to 0 if the list is empty?

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

采纳答案by Marc Gravell

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

Note the conversion to decimal?. You'll get an empty result if there are none (just handle that after the fact - I'm mainly illustrating how to stop the exception). I also made "non-zero" use !=rather than >.

注意转换为decimal?. 如果没有结果,您将得到一个空结果(事后处理它 - 我主要说明如何停止异常)。我还使用了“非零”!=而不是>.

回答by Christoffer Lette

What you want is this:

你想要的是这个:

IEnumerable<double> results = ... your query ...

double result = results.MinOrDefault();

Well, MinOrDefault()does not exist. But if we were to implement it ourselves it would look something like this:

嗯,MinOrDefault()不存在。但是如果我们自己实现它,它看起来像这样:

public static class EnumerableExtensions
{
    public static T MinOrDefault<T>(this IEnumerable<T> sequence)
    {
        if (sequence.Any())
        {
            return sequence.Min();
        }
        else
        {
            return default(T);
        }
    }
}

However, there is functionality in System.Linqthat will produce the same result (in a slightly different way):

但是,有一些功能System.Linq会产生相同的结果(以稍微不同的方式):

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

If the resultssequence contains no elements, DefaultIfEmpty()will produce a sequence containing one element - the default(T)- which you subsequently can call Min()on.

如果results序列不包含任何元素,DefaultIfEmpty()将生成一个包含一个元素的序列default(T)- 您随后可以调用该元素Min()

If the default(T)is not what you want, then you could specify your own default with:

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

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

Now, that's neat!

现在,这很整洁!

回答by JDandChips

This approach will return the single smallest Amountvalue from itemList. In theory this shouldavoid multiple round trips to the database.

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

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

The null reference exception is no longer caused because we are using a nullable type.

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

By avoiding the use of executing methods such as Anybefore calling Min, we should only be making one trip to the database

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

回答by Jon Hanna

The neatest in terms of just doing it once in a small amount code is, as already mentioned:

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

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

With casting itm.Amountto decimal?and obtaining the Minof that being the neatest if we want to be able to detect this empty condition.

如果我们希望能够检测到这个空的条件,那么使用强制itm.Amount转换decimal?和获取Min最简单的方法。

If however you want to actually provide a MinOrDefault()then we can of course start with:

但是,如果您想实际提供一个,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);
}

You now have a full set of MinOrDefaultwhether or not you include a selector, and whether or not you specify the default.

您现在可以了解MinOrDefault是否包含选择器以及是否指定默认值。

From this point on your code is simply:

从这一点开始,您的代码很简单:

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

So, while it's not as neat to begin with, it's neater from then on.

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

But wait! There's more!

可是等等!还有更多!

Let's say you use EF and want to make use of the asyncsupport. Easily done:

假设您使用 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 that I don't use awaithere; we can directly create a Task<TSource>that does what we need without it, and hence avoid the hidden complications awaitbrings).

(请注意,我await在这里不使用;我们可以直接创建一个Task<TSource>不需要它就能做我们需要的,从而避免隐藏的复杂性await带来的)。

But wait, there's more! Let's say we're using this with IEnumerable<T>some times. Our approach is sub-optimal. Surely we can do better!

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

First, the Mindefined on int?, long?, float?double?and decimal?already do what we want anyway (as Marc Gravell's answer makes use of). Similarly, we also get the behaviour we want from the Minalready defined if called for any other T?. So let's do some small, and hence easily inlined, methods to take advantage of this fact:

首先,Min定义上int?long?float?double?decimal?已经做我们想做(就像马克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);
}

Now let's start with the more general case first:

现在让我们先从更一般的情况开始:

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

Now the obvious overrides that make use of this:

现在使用这个的明显覆盖:

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

If we're really bullish about performance, we can optimise for certain cases, just like Enumerable.Min()does:

如果我们真的看好性能,我们可以针对某些情况进行优化,就像这样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();
}

And so on for long, float, doubleand decimalto match the set of Min()provided by Enumerable. This is the sort of thing where T4 templates are useful.

等了longfloatdoubledecimal相匹配的一套Min()提供Enumerable。这就是 T4 模板有用的地方。

At the end of all that, we have just about as performant an implementation of MinOrDefault()as we could hope for, for a wide range of types. Certainly not "neat" in the face of one use for it (again, just use DefaultIfEmpty().Min()), but very much "neat" if we find ourselves using it a lot, so we have a nice library we can reuse (or indeed, paste into answers on StackOverflow…).

最后MinOrDefault(),对于各种类型,我们已经实现了我们所希望的性能。面对它的一种用途当然不是“整洁”(再次,只是使用DefaultIfEmpty().Min()),但是如果我们发现自己经常使用它,那就非常“整洁”了,所以我们有一个可以重用的不错的库(或者实际上,粘贴到StackOverflow 上的答案......)。

回答by Jason

If itemList is non-nullable (where DefaultIfEmpty gives 0) and you want null as a potential output value, you can use the lambda syntax as well:

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

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