Die sauberste in Bezug auf die nur einmal tun es in einer kleinen Menge Code ist, wie bereits erwähnt:
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).DefaultIfEmpty().Min();
Mit Guss itm.Amount
zu decimal?
und die Erlangung der Min
Das ist das Schönste, wenn wir diesen leeren Zustand erkennen wollen.
Wenn Sie jedoch tatsächlich eine MinOrDefault()
dann können wir natürlich damit beginnen:
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 von MinOrDefault
ob Sie einen Selektor einschließen oder nicht und ob Sie den Standardwert angeben oder nicht.
Von diesem Punkt an ist Ihr Code einfach:
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).MinOrDefault();
Am Anfang ist es zwar nicht so ordentlich, aber von da an ist es ordentlicher.
Doch halt! Es gibt noch mehr!
Nehmen wir an, Sie verwenden EF und möchten die async
Unterstützung. Das ist ganz einfach:
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 keine await
hier; wir können direkt eine Task<TSource>
die das, was wir brauchen, auch ohne sie tut, und somit die versteckten Komplikationen vermeidet await
bringt).
Aber halt, da ist noch mehr! Nehmen wir an, wir verwenden dies mit IEnumerable<T>
einige Male. Unser Ansatz ist suboptimal. Sicherlich können wir es besser machen!
Erstens, die Min
festgelegt am int?
, long?
, float?
double?
und decimal?
ohnehin schon das tun, was wir wollen (wie in der Antwort von Marc Gravell). In ähnlicher Weise erhalten wir das gewünschte Verhalten auch von der Min
bereits definiert, wenn sie für eine andere T?
. Lassen Sie uns also einige kleine und daher leicht einzubindende Methoden entwickeln, 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 zunächst 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 zu den offensichtlichen Überschreibungen, die sich dies zunutze 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 es uns wirklich auf die Leistung ankommt, können wir für bestimmte Fälle optimieren, so wie Enumerable.Min()
tut:
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
mit dem Satz von Min()
zur Verfügung gestellt von Enumerable
. Dies ist die Art von Dingen, bei denen T4-Vorlagen nützlich sind.
Am Ende haben wir eine fast ebenso leistungsfähige Implementierung von MinOrDefault()
wie wir es uns erhoffen konnten, für eine breite Palette von Typen. Sicherlich nicht "ordentlich" angesichts der einen Verwendung dafür (wieder, verwenden Sie einfach DefaultIfEmpty().Min()
), aber sehr "ordentlich", wenn wir es oft verwenden, so dass wir eine schöne Bibliothek haben, die wir wiederverwenden können (oder in Antworten auf StackOverflow einfügen können ).