Каков самый аккуратный способ достижения "MinOrDefault" в Linq?


Я создаю список десятичных значений из выражения linq, и мне нужно минимальное ненулевое значение. Однако вполне возможно, что выражение linq приведет к пустому списку.

это вызовет исключение, и нет никакого MinOrDefault, чтобы справиться с этой ситуацией.

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

каков самый аккуратный способ установить результат в 0, если список пуст?

4 66

4 ответа:

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

обратите внимание на преобразование в decimal?. Вы получите пустой результат, если его нет (просто обработайте это после факта - я в основном иллюстрирую, как остановить исключение). Я также сделал" ненулевое " использование !=, а не >.

то, что вы хотите это:

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

вот это здорово!

самый аккуратный с точки зрения просто сделать это один раз в небольшом количестве кода, как уже упоминалось:

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? уже делаем то, что мы хотим в любом случае (как использует ответ Марка Гравелла). Аналогично, мы также получаем поведение, которое мы хотим от 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() как мы могли надеяться, для широкого спектра типов. Конечно, не "аккуратно" перед лицом одного использования для него (опять же, просто используйте DefaultIfEmpty().Min()), но очень "аккуратно", если мы много используем его, поэтому у нас есть хорошая библиотека, которую мы можем повторно использовать (или действительно вставить в ответы на StackOverflow...).

этот подход вернет один маленький Amount значение itemList. В теории это должны избегайте нескольких поездок туда и обратно в базу данных.

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

исключение null reference больше не вызывается, потому что мы используем тип nullable.

избегая использования методов выполнения, таких как Any перед вызовом Min, мы должны сделать только одну поездку в базу данных