Estoy produciendo una lista de valores decimales a partir de una expresión linq y quiero el valor mínimo distinto de cero. Sin embargo, es muy posible que la expresión linq resulte en una lista vacía.
Esto generará una excepción y no hay MinOrDefault para hacer frente a esta situación.
decimal result = (from Item itm in itemList
where itm.Amount > 0
select itm.Amount).Min();
¿Cuál es la forma más ordenada de establecer el resultado en 0 si la lista está vacía?
Respuestas:
decimal? result = (from Item itm in itemList where itm.Amount != 0 select (decimal?)itm.Amount).Min();
Tenga en cuenta la conversión a
decimal?
. Obtendrá un resultado vacío si no hay ninguno (solo maneje eso después del hecho; estoy ilustrando principalmente cómo detener la excepción). También hice uso "distinto de cero" en!=
lugar de>
.fuente
decimal? result = (new decimal?[0]).Min();
regalanull
Lo que quieres es esto:
IEnumerable<double> results = ... your query ... double result = results.MinOrDefault();
Bueno,
MinOrDefault()
no existe. Pero si lo implementamos nosotros mismos, se vería así:public static class EnumerableExtensions { public static T MinOrDefault<T>(this IEnumerable<T> sequence) { if (sequence.Any()) { return sequence.Min(); } else { return default(T); } } }
Sin embargo, hay una funcionalidad
System.Linq
que producirá el mismo resultado (de una manera ligeramente diferente):double result = results.DefaultIfEmpty().Min();
Si la
results
secuencia no contiene elementos,DefaultIfEmpty()
producirá una secuencia que contenga un elemento, eldefault(T)
, al que puede llamar posteriormenteMin()
.Si
default(T)
no es lo que desea, puede especificar su propio valor predeterminado con:double myDefault = ... double result = results.DefaultIfEmpty(myDefault).Min();
¡Eso es genial!
fuente
DefaultIfEmpty
, de hecho se implementa de manera inteligente, solo se reenvía la secuencia si hay elementos que usanyield return
s.DefaultIfEmpty
que toma unIEnumerable<T>
. Si lo llamó en unIQueryable<T>
, como lo haría con una operación de base de datos, entonces no devuelve una secuencia singleton, sino que genera una apropiadaMethodCallExpression
, por lo que la consulta resultante no requiere que se recupere todo. SinEnumerableExtensions
embargo, el enfoque sugerido aquí tiene ese problema.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
adecimal?
y obtener laMin
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 unTask<TSource>
que haga lo que necesitamos sin él y, por lo tanto, evitar las complicaciones ocultas queawait
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 enint?
,long?
,float?
double?
ydecimal?
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 delMin
ya definido si se solicita cualquier otroT?
. 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) //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; }
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
ydecimal
para que coincida con el conjunto deMin()
proporcionada porEnumerable
. 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 useDefaultIfEmpty().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 ...).fuente
Este enfoque devolverá el
Amount
valor más pequeño deitemList
. En teoría, esto debería evitar múltiples viajes de ida y vuelta a la base de datos.decimal? result = (from Item itm in itemList where itm.Amount > 0) .Min(itm => (decimal?)itm.Amount);
La excepción de referencia nula ya no se produce porque estamos usando un tipo que acepta valores NULL.
Al evitar el uso de métodos de ejecución como
Any
antes de llamarMin
, solo deberíamos hacer un viaje a la base de datosfuente
Select
en la respuesta aceptada ejecutaría la consulta más de una vez? La respuesta aceptada resultaría en una sola llamada DB.Select
es un método diferido y no provocaría una ejecución. He eliminado estas mentiras de mi respuesta. Referencia: "Pro ASP.NET MVC4" de Adam Freeman (libro)Si itemList no admite nulos (donde DefaultIfEmpty da 0) y desea nulo como valor de salida potencial, también puede usar la sintaxis lambda:
decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);
fuente