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();regalanullLo 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.Linqque producirá el mismo resultado (de una manera ligeramente diferente):double result = results.DefaultIfEmpty().Min();Si la
resultssecuencia 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 returns.DefaultIfEmptyque 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. SinEnumerableExtensionsembargo, 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.Amountadecimal?y obtener laMinde 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
MinOrDefaultsi 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
asyncsoporte. 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
awaitaquí; podemos crear directamente unTask<TSource>que haga lo que necesitamos sin él y, por lo tanto, evitar las complicaciones ocultas queawaittrae).¡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
Mindefinida 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 delMinya 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,doubleydecimalpara 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
Amountvalor 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
Anyantes de llamarMin, solo deberíamos hacer un viaje a la base de datosfuente
Selecten la respuesta aceptada ejecutaría la consulta más de una vez? La respuesta aceptada resultaría en una sola llamada DB.Selectes 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