Какой самый лучший способ достичь MinOrDefault в Linq? - PullRequest
71 голосов
/ 30 января 2010

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

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

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

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

Ответы [ 4 ]

117 голосов
/ 30 января 2010

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

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() on.

Если default(T) не то, что вы хотите, то вы можете указать свой собственный по умолчанию с помощью:

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

Так вот, это здорово!

51 голосов
/ 30 января 2010
decimal? result = (from Item itm in itemList
                  where itm.Amount != 0
                  select (decimal?)itm.Amount).Min();

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

10 голосов
/ 15 мая 2015

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

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…).

0 голосов
/ 15 мая 2015

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...