Tworzę listę wartości dziesiętnych z wyrażenia linq i chcę minimalną wartość niezerową. Jednak jest całkowicie możliwe, że wyrażenie linq spowoduje powstanie pustej listy.
Spowoduje to wyjątek i nie ma MinOrDefault, który poradziłby sobie z tą sytuacją.
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).Min();
Jaki jest najładniejszy sposób ustawienia wyniku na 0, jeśli lista jest pusta?
Odpowiedzi:
decimal? result = (from Item itm in itemList where itm.Amount != 0 select (decimal?)itm.Amount).Min();
Zwróć uwagę na konwersję do
decimal?
. Otrzymasz pusty wynik, jeśli go nie ma (po prostu zajmij się tym po fakcie - głównie ilustruję, jak zatrzymać wyjątek). Użyłem też!=
raczej wartości „niezerowej” niż>
.źródło
decimal? result = (new decimal?[0]).Min();
dajenull
Chcesz to:
IEnumerable<double> results = ... your query ... double result = results.MinOrDefault();
Cóż,
MinOrDefault()
nie istnieje. Ale gdybyśmy sami to zaimplementowali, wyglądałoby to mniej więcej tak:public static class EnumerableExtensions { public static T MinOrDefault<T>(this IEnumerable<T> sequence) { if (sequence.Any()) { return sequence.Min(); } else { return default(T); } } }
Istnieje jednak funkcjonalność,
System.Linq
która da ten sam wynik (w nieco inny sposób):double result = results.DefaultIfEmpty().Min();
Jeśli
results
sekwencja nie zawiera żadnych elementów,DefaultIfEmpty()
utworzy sekwencję zawierającą jeden element - thedefault(T)
- do którego możesz następnie przywołaćMin()
.Jeśli
default(T)
nie jest tym, czego chcesz, możesz określić własne ustawienie domyślne za pomocą:double myDefault = ... double result = results.DefaultIfEmpty(myDefault).Min();
Teraz jest fajnie!
źródło
DefaultIfEmpty
, jest on rzeczywiście zaimplementowany inteligentnie, przekazuje sekwencję tylko wtedy, gdy istnieją elementy używająceyield return
s.DefaultIfEmpty
która przyjmuje rozszerzenieIEnumerable<T>
. Jeśli wywołałeś go w operacjiIQueryable<T>
, tak jak w przypadku operacji na bazie danych, to nie zwraca pojedynczej sekwencji, ale generuje odpowiedniąMethodCallExpression
, więc wynikowe zapytanie nie wymaga pobrania wszystkiego. SugerowaneEnumerableExtensions
tutaj podejście ma jednak ten problem.Jak już wspomniano, najładniejszy pod względem zrobienia tego raz w małej ilości kodu jest:
decimal result = (from Item itm in itemList where itm.Amount > 0 select itm.Amount).DefaultIfEmpty().Min();
Z odlewania
itm.Amount
dodecimal?
i uzyskiwaniaMin
z tego bycia neatest jeśli chcemy być w stanie wykryć tę pustą warunek.Jeśli jednak chcesz faktycznie zapewnić
MinOrDefault()
, możemy oczywiście zacząć od: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); }
Masz teraz pełny zestaw tego,
MinOrDefault
czy chcesz dołączyć selektor i czy określasz wartość domyślną, czy nie.Od tego momentu Twój kod jest po prostu:
decimal result = (from Item itm in itemList where itm.Amount > 0 select itm.Amount).MinOrDefault();
Tak więc, chociaż na początku nie jest tak schludnie, odtąd jest schludniej.
Ale poczekaj! Jest więcej!
Powiedzmy, że używasz EF i chcesz skorzystać ze
async
wsparcia. Łatwe do zrobienia: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); }
(Zwróć uwagę, że nie używam
await
tutaj; możemy bezpośrednio stworzyćTask<TSource>
bez niego to, czego potrzebujemy, a tym samym uniknąć ukrytych komplikacjiawait
).Ale czekaj, jest więcej! Powiedzmy, że używamy tego
IEnumerable<T>
czasami. Nasze podejście jest nieoptymalne. Z pewnością możemy zrobić to lepiej!Po pierwsze,
Min
określa się naint?
,long?
,float?
double?
adecimal?
już w każdym razie to, co my chcemy (jako odpowiedź marki Marc Gravell korzystają z). Podobnie, otrzymujemy również zachowanie, które chcemy, zMin
już zdefiniowanego, jeśli zostanie wywołane do innegoT?
. Zróbmy więc kilka małych, a przez to łatwych do wprowadzenia metod, aby skorzystać z tego faktu: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); }
Teraz zacznijmy od bardziej ogólnego przypadku:
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; }
Teraz oczywiste nadpisania, które wykorzystują to:
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(); }
Jeśli naprawdę zależy nam na wydajności, możemy zoptymalizować ją pod kątem określonych przypadków, tak jak
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(); }
I tak dalej
long
,float
,double
idecimal
dopasować zestawMin()
zapewniaEnumerable
. W takich sytuacjach przydatne są szablony T4.Na koniec mamy tak wydajną implementację,
MinOrDefault()
na jaką mogliśmy liczyć, dla szerokiej gamy typów. Z pewnością nie „schludny” w obliczu jednego użycia do tego (ponownie, po prostu użyjDefaultIfEmpty().Min()
), ale bardzo „schludny”, jeśli często go używamy, więc mamy fajną bibliotekę, którą możemy ponownie wykorzystać (lub rzeczywiście wkleić do odpowiedzi na StackOverflow…).źródło
To podejście zwróci pojedynczą najmniejszą
Amount
wartość zitemList
. Teoretycznie powinno to zapobiec wielokrotnym podróżom w obie strony do bazy danych.decimal? result = (from Item itm in itemList where itm.Amount > 0) .Min(itm => (decimal?)itm.Amount);
Wyjątek odwołania o wartości null nie jest już powodowany, ponieważ używamy typu dopuszczającego wartość null.
Unikając wykonywania metod, takich jak
Any
przed wywołaniemMin
, powinniśmy odbyć tylko jedną podróż do bazy danychźródło
Select
w zaakceptowanej odpowiedzi spowodowałoby wykonanie zapytania więcej niż jeden raz? Zaakceptowana odpowiedź spowodowałaby pojedyncze wywołanie DB.Select
jest to metoda odroczona i nie spowodowałaby wykonania. Usunąłem te kłamstwa z mojej odpowiedzi. Źródła: „Pro ASP.NET MVC4” autorstwa Adama Freemana (książka)Jeśli itemList nie dopuszcza wartości null (gdzie DefaultIfEmpty daje 0) i chcesz mieć wartość null jako potencjalną wartość wyjściową, możesz również użyć składni lambda:
decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);
źródło