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
auf decimal?
und Erhalt der Min
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 erstellen Task<TSource>
, das das tut, was wir brauchen, ohne es zu verwenden, und somit die versteckten Komplikationen vermeiden await
).
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 auf int?
, long?
, float?
double?
und decimal?
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 dem Min
bereits definierten, wenn es für ein anderes aufgerufen wird T?
. 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)
{
var result = source.Min();
return result == null ? defaultValue : result;
}
else
{
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
und decimal
die Menge des entsprechen , Min()
bereitgestellt durch Enumerable
. 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 verwenden DefaultIfEmpty().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…).