Ich erstelle eine Liste von Dezimalwerten aus einem Linq-Ausdruck und möchte den minimalen Wert ungleich Null. Es ist jedoch durchaus möglich, dass der linq-Ausdruck zu einer leeren Liste führt.
Dies löst eine Ausnahme aus und es gibt keinen MinOrDefault, der mit dieser Situation fertig wird.
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).Min();
Was ist der beste Weg, um das Ergebnis auf 0 zu setzen, wenn die Liste leer ist?
Antworten:
decimal? result = (from Item itm in itemList where itm.Amount != 0 select (decimal?)itm.Amount).Min();
Beachten Sie die Konvertierung in
decimal?
. Sie erhalten ein leeres Ergebnis, wenn es keines gibt (behandeln Sie das einfach nachträglich - ich zeige hauptsächlich, wie Sie die Ausnahme stoppen können). Ich habe auch "ungleich Null" verwendet!=
anstatt>
.quelle
decimal? result = (new decimal?[0]).Min();
gibtnull
Was Sie wollen, ist Folgendes:
IEnumerable<double> results = ... your query ... double result = results.MinOrDefault();
Nun,
MinOrDefault()
existiert nicht. Aber wenn wir es selbst implementieren würden, würde es ungefähr so aussehen:public static class EnumerableExtensions { public static T MinOrDefault<T>(this IEnumerable<T> sequence) { if (sequence.Any()) { return sequence.Min(); } else { return default(T); } } }
Es gibt jedoch Funktionen
System.Linq
, die das gleiche Ergebnis liefern (auf etwas andere Weise):double result = results.DefaultIfEmpty().Min();
Wenn die
results
Sequenz keine Elemente enthält,DefaultIfEmpty()
wird eine Sequenz erstellt, die ein Element enthält - dasdefault(T)
-, das Sie anschließend aufrufen könnenMin()
.Wenn das
default(T)
nicht das ist, was Sie wollen, können Sie Ihren eigenen Standard angeben mit:double myDefault = ... double result = results.DefaultIfEmpty(myDefault).Min();
Nun, das ist ordentlich!
quelle
DefaultIfEmpty
, ist sie in der Tat intelligent implementiert und leitet die Sequenz nur weiter, wenn es Elemente gibt, dieyield return
s verwenden.DefaultIfEmpty
, nimmt eineIEnumerable<T>
. Wenn Sie es auf einem aufgerufen habenIQueryable<T>
, wie Sie es bei einer Datenbankoperation getan hätten, gibt es keine Singleton-Sequenz zurück, sondern generiert eine entsprechendeMethodCallExpression
, sodass für die resultierende Abfrage nicht alles abgerufen werden muss. Der hier vorgeschlageneEnumerableExtensions
Ansatz hat dieses Problem jedoch.Das Schönste daran, es nur einmal in einem kleinen Mengencode zu tun, ist, wie bereits erwähnt:
decimal result = (from Item itm in itemList where itm.Amount > 0 select itm.Amount).DefaultIfEmpty().Min();
Mit Gießen
itm.Amount
aufdecimal?
und Erhalt derMin
des Seins , die sauberste , wenn wir diesen leeren Zustand in der Lage sein erkennen wollen.Wenn Sie jedoch tatsächlich eine bereitstellen möchten,
MinOrDefault()
können wir natürlich beginnen mit: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); }
Sie haben jetzt einen vollständigen Satz darüber,
MinOrDefault
ob Sie einen Selektor einschließen oder nicht und ob Sie den Standard angeben oder nicht.Ab diesem Punkt ist Ihr Code einfach:
decimal result = (from Item itm in itemList where itm.Amount > 0 select itm.Amount).MinOrDefault();
Also, obwohl es anfangs nicht so ordentlich ist, ist es von da an ordentlicher.
Aber warte! Es gibt mehr!
Angenommen, Sie verwenden EF und möchten die
async
Unterstützung nutzen. Einfach gemacht: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); }
(Beachten Sie, dass ich
await
hier nicht verwende ; wir können direkt ein erstellenTask<TSource>
, das das tut, was wir brauchen, ohne es zu verwenden, und somit die versteckten Komplikationen vermeidenawait
).Aber warte, da ist noch mehr! Nehmen wir an, wir verwenden dies
IEnumerable<T>
einige Male. Unser Ansatz ist nicht optimal. Sicher können wir es besser machen!Erstens, die
Min
definiert aufint?
,long?
,float?
double?
unddecimal?
schon tun , was wir so wie man will (wie Marc GRA Antwort Marken verwenden). In ähnlicher Weise erhalten wir auch das gewünschte Verhalten von demMin
bereits definierten, wenn es für ein anderes aufgerufen wirdT?
. Lassen Sie uns also einige kleine und daher leicht zu beschreibende Methoden anwenden, um diese Tatsache auszunutzen: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); }
Beginnen wir nun mit dem allgemeineren Fall:
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; }
Nun die offensichtlichen Überschreibungen, die davon Gebrauch machen:
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(); }
Wenn wir in Bezug auf die Leistung wirklich optimistisch sind, können wir für bestimmte Fälle optimieren, genau wie
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(); }
Und so weiter für
long
,float
,double
unddecimal
die Menge des entsprechen ,Min()
bereitgestellt durchEnumerable
. Dies ist die Art von Dingen, bei denen T4-Vorlagen nützlich sind.Am Ende all dessen haben wir eine so performante Implementierung,
MinOrDefault()
wie wir es uns erhoffen können, für eine Vielzahl von Typen. Sicherlich nicht "ordentlich" angesichts einer Verwendung dafür (wieder nur verwendenDefaultIfEmpty().Min()
), sondern sehr "ordentlich", wenn wir es häufig verwenden, so dass wir eine schöne Bibliothek haben, die wir wiederverwenden (oder tatsächlich einfügen) können Antworten auf StackOverflow…).quelle
Dieser Ansatz gibt den kleinsten Einzelwert
Amount
von zurückitemList
. Theoretisch sollte dies mehrere Roundtrips zur Datenbank vermeiden.decimal? result = (from Item itm in itemList where itm.Amount > 0) .Min(itm => (decimal?)itm.Amount);
Die Nullreferenzausnahme wird nicht mehr verursacht, da wir einen nullbaren Typ verwenden.
Indem Sie die Verwendung von Ausführungsmethoden wie
Any
vor dem Aufruf vermeidenMin
, sollten wir nur eine Reise in die Datenbank unternehmenquelle
Select
in der akzeptierten Antwort die Abfrage mehr als einmal ausführen würde? Die akzeptierte Antwort würde zu einem einzelnen DB-Aufruf führen.Select
ist eine verzögerte Methode und würde keine Ausführung verursachen. Ich habe diese Lügen aus meiner Antwort entfernt. Referenz: "Pro ASP.NET MVC4" von Adam Freeman (Buch)Wenn itemList nicht nullbar ist (wobei DefaultIfEmpty 0 angibt) und Sie null als potenziellen Ausgabewert möchten, können Sie auch die Lambda-Syntax verwenden:
decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);
quelle