Lo mejor en términos de hacerlo una vez en una pequeña cantidad de código es, como ya se mencionó:
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).DefaultIfEmpty().Min();
Con fundición itm.Amount
a decimal?
y obtener la Min
de que siendo el más bonito si queremos ser capaces de detectar esta condición vacía.
Sin embargo, si realmente desea proporcionar un MinOrDefault()
, por supuesto, podemos comenzar con:
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);
}
Ahora tiene un conjunto completo de MinOrDefault
si incluye o no un selector, y si especifica o no el predeterminado.
A partir de este punto, su código es simplemente:
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).MinOrDefault();
Entonces, aunque no es tan ordenado para empezar, está más ordenado a partir de ese momento.
¡Pero espera! ¡Hay más!
Digamos que usa EF y quiere hacer uso del async
soporte. Fácil de hacer:
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);
}
(Tenga en cuenta que no lo uso await
aquí; podemos crear directamente un Task<TSource>
que haga lo que necesitamos sin él y, por lo tanto, evitar las complicaciones ocultas que await
trae).
¡Pero espera hay mas! Digamos que estamos usando esto IEnumerable<T>
algunas veces. Nuestro enfoque no es óptimo. ¡Sin duda, podemos hacerlo mejor!
En primer lugar, la Min
definida en int?
, long?
, float?
double?
y decimal?
ya hacemos lo que queremos todos modos (como marcas de respuesta de Marc Gravell uso de). Del mismo modo, también obtenemos el comportamiento que queremos del Min
ya definido si se solicita cualquier otro T?
. Así que hagamos algunos métodos pequeños y, por lo tanto, fáciles de integrar para aprovechar este hecho:
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);
}
Ahora comencemos con el caso más general primero:
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;
}
Ahora las anulaciones obvias que hacen uso de esto:
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();
}
Si somos realmente optimistas sobre el rendimiento, podemos optimizar para ciertos casos, al igual que lo Enumerable.Min()
hace:
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();
}
Y así sucesivamente para long
, float
, double
y decimal
para que coincida con el conjunto de Min()
proporcionada por Enumerable
. Este es el tipo de cosas en las que las plantillas T4 son útiles.
Al final de todo eso, tenemos una implementación casi tan eficaz MinOrDefault()
como podríamos esperar, para una amplia gama de tipos. Ciertamente no es "ordenado" a la vista de un uso para él (nuevamente, solo use DefaultIfEmpty().Min()
), pero sí mucho "ordenado" si lo usamos mucho, por lo que tenemos una biblioteca agradable que podemos reutilizar (o de hecho, pegar respuestas sobre StackOverflow ...).