Я создаю список десятичных значений из выражения linq, и мне нужно минимальное ненулевое значение. Однако вполне возможно, что выражение linq приведет к пустому списку.
Это вызовет исключение, и нет MinOrDefault, чтобы справиться с этой ситуацией.
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).Min();
Какой самый простой способ установить результат на 0, если список пуст?
Ответы:
decimal? result = (from Item itm in itemList where itm.Amount != 0 select (decimal?)itm.Amount).Min();
Обратите внимание на преобразование в
decimal?
. Вы получите пустой результат, если его нет (просто обработайте это постфактум - я в основном показываю, как остановить исключение). Я также сделал "ненулевое" использование,!=
а не>
.источник
decimal? result = (new decimal?[0]).Min();
даетnull
Что вы хотите:
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();
Вот это здорово!
источник
DefaultIfEmpty
, он действительно реализован умно, только пересылка последовательности выполняется, если есть элементы, использующиеyield return
s.DefaultIfEmpty
которая принимаетIEnumerable<T>
. Если вы вызвали егоIQueryable<T>
, например, для операции с базой данных, тогда он не возвращает одноэлементную последовательность, но генерирует соответствующуюMethodCallExpression
, и поэтому результирующий запрос не требует, чтобы все было извлечено. Однако предлагаемыйEnumerableExtensions
здесь подход имеет эту проблему.Самым изящным с точки зрения того, чтобы сделать это один раз в небольшом количестве кода, является, как уже упоминалось:
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?
уже делать то , что мы хотим , чтобы в любом случае (как ответ марки Marc Gravell по использованию). Точно так же мы также получаем желаемое поведение от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.
Избегая использования методов выполнения, таких как
Any
перед вызовомMin
, мы должны совершить только одно посещение базы данных.источник
Select
в принятом ответе приведет к выполнению запроса более одного раза? Принятый ответ приведет к одному вызову БД.Select
это отложенный метод и не вызовет выполнения. Я удалил эту ложь из своего ответа. Ссылка: «Pro ASP.NET MVC4» от Адама Фримана (книга)Если itemList не допускает значения NULL (где DefaultIfEmpty дает 0) и вы хотите, чтобы NULL в качестве потенциального выходного значения, вы также можете использовать синтаксис лямбда:
decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);
источник