Какой самый лучший способ добиться "MinOrDefault" в Linq?

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

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

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

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

Ответ 1

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

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

Ответ 2

Что вы хотите, так это:

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

Теперь, это аккуратно!

Ответ 3

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

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

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

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

Исключение нулевой ссылки больше не вызвано тем, что мы используем тип с нулевым значением.

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

Ответ 5

Если itemList не обнуляется (где DefaultIfEmpty дает 0), и вы хотите установить нулевое значение в качестве потенциального выходного значения, вы можете также использовать лямбда-синтаксис:

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