The simplest in terms of simple execution of this code in a small amount of code, as already mentioned:
decimal result = (from Item itm in itemList where itm.Amount > 0 select itm.Amount).DefaultIfEmpty().Min();
With casting itm.Amount to decimal? and get Min of what is the most accurate if we want to discover this empty condition.
If you really want to provide MinOrDefault() , then we can, of course, start with:
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); }
You now have the full set of MinOrDefault , whether you enable the selector or whether you specify the default.
From now on, your code is simple:
decimal result = (from Item itm in itemList where itm.Amount > 0 select itm.Amount).MinOrDefault();
So, although it is not so neat to start from this moment, it is neat from this moment.
But wait! There are more!
Suppose you are using EF and want to use async support. Easy done:
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); }
(Note that I am not using await , we can directly create a Task<TSource> that does what we need without it, and therefore avoid await hidden complications).
But wait, there still! Let's say we use this with IEnumerable<T> several times. Our approach is suboptimal. Of course we can do better!
First, Min defined in int? long? float? double? and decimal? already do what we want, anyway (as using the answer of Mark Gravel). Similarly, we also get the behavior that we want from the already specified t23, if it is called for any other T? . Therefore, let's make some small and, therefore, easily built-in methods to take advantage of this fact:
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); }
Now, start over with a more general case:
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; }
Now the obvious overrides that use this are:
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(); }
If we are really optimistic about performance, we can optimize for certain cases, just as Enumerable.Min() does:
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(); }
And so on for long , float , double and decimal to match the Min() set provided by Enumerable . This is the thing that uses T4 templates.
At the end of all this, we have about the same implementation of MinOrDefault() , as we might hope, for a wide range of types. Of course, not “neat” in the face of one use for it (again, just use DefaultIfEmpty().Min() ), but very “neat” if we use it a lot, so we have a good library that we can reuse use (or really, paste in StackOverflow answers ...).