Каков самый аккуратный способ достижения "MinOrDefault" в Linq?
Я создаю список десятичных значений из выражения linq, и мне нужно минимальное ненулевое значение. Однако вполне возможно, что выражение linq приведет к пустому списку.
это вызовет исключение, и нет никакого MinOrDefault, чтобы справиться с этой ситуацией.
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).Min();
каков самый аккуратный способ установить результат в 0, если список пуст?
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
, мы должны сделать только одну поездку в базу данных