98

I'm producing a list of decimal values from a LINQ expression and I want the minimum non zero value. However it's entirely possible that the LINQ expression will result in an empty list.

This will raise an exception and there is no MinOrDefault to cope with this situation.

decimal result = (from Item itm in itemList
                  where itm.Amount > 0
                  select itm.Amount).Min();

How can I set the result to 0 if the list is empty?

d219
  • 2,707
  • 5
  • 31
  • 36
Chris Simpson
  • 7,821
  • 10
  • 48
  • 68

6 Answers6

152

What you want is this:

IEnumerable<double> results = ... your query ...

double result = results.MinOrDefault();

Well, MinOrDefault() does not exist. But if we were to implement it ourselves it would look something like this:

public static class EnumerableExtensions
{
    public static T MinOrDefault<T>(this IEnumerable<T> sequence)
    {
        if (sequence.Any())
        {
            return sequence.Min();
        }
        else
        {
            return default(T);
        }
    }
}

However, there is functionality in System.Linq that will produce the same result (in a slightly different way):

double result = results.DefaultIfEmpty().Min();

If the results sequence contains no elements, DefaultIfEmpty() will produce a sequence containing one element - the default(T) - which you subsequently can call Min() on.

If the default(T) is not what you want, then you could specify your own default with:

double myDefault = ...
double result = results.DefaultIfEmpty(myDefault).Min();

Now, that's neat!

Christoffer Lette
  • 14,346
  • 7
  • 50
  • 58
  • damn, that's so obvious now you say it, thank you. I may actually go with: ...DefaultIfEmpty(decimal.MaxValue).Min() – Chris Simpson Feb 01 '10 at 17:09
  • I like your answer best. I think the other solution might be much slower because of boxing/unboxing. – Christo Sep 28 '10 at 06:54
  • Sadly, your last solution with `DefaultIfEmpty` does not work with Lambda expressions in the Min method, because of the null return. Would have been perfect. Still, it's a good solution for simple/comparable types. – Adrian Marinica Aug 01 '12 at 12:33
  • @AdrianMar: Sounds like your `T` is a reference type, which could introduce nulls in the sequence (`DefaultIfEmpty(...)` will of course do that if you don't provide a specific (non-null) default). However, as long as the sequence itself isn't null and as long as your `T` implements `IComparable`, `Min(...)` _should_ work. Could you tell me more about the problem your experiencing? – Christoffer Lette Aug 02 '12 at 12:18
  • 1
    @ChristofferLette I want just an empty list of T, so I also ended up using Any() with Min(). Thanks! – Adrian Marinica Aug 02 '12 at 12:45
  • 1
    @AdrianMar: BTW, did you consider using a [Null Object](http://en.wikipedia.org/wiki/Null_Object_pattern) as the default? – Christoffer Lette Aug 02 '12 at 13:27
  • @ChristofferLette That would have been a nice idea, thanks for it. If I will have some time in the future, I will implement it properly. For now, the task is too small in the whole picture of things to be given to much consideration. Again, thanks for the tip, it would have suited me perfectly! – Adrian Marinica Aug 02 '12 at 13:32
  • 23
    The MinOrDefault implementation mentioned here will iterate over the enumerable twice. It doesn't matter for in-memory collections, but for LINQ to Entity or lazy "yield return" built enumerables, this means two round-trips to the database or processing the first element twice. I prefer the results.DefaultIfEmpty(myDefault).Min() solution. – Kevin Coulombe Jul 11 '13 at 20:57
  • @Kevin Yes, you are of course correct. The call to `.Any()` is the culprit here, although it won't iterate over the entire sequence. I guess that the standard implementation of `DefaultIfEmpty` will allow an existing sequence to just pass through, avoiding the second iteration. – Christoffer Lette Aug 12 '13 at 10:10
  • 1
    Couldn't believe I didn't know about DefaultIfEmpty()! Thanks! – Gerardo Contijoch Sep 19 '14 at 14:05
  • 4
    Looking at the source for `DefaultIfEmpty`, it is indeed implemented smart, only does forwarding of the sequence if there are elements using `yield return`s. – Peter Lillevold Oct 08 '14 at 08:08
  • `DefaultIfEmpty()` "Returns the elements of the specified sequence or the type parameter's default value in a singleton collection if the sequence is empty." https://msdn.microsoft.com/en-us/library/vstudio/bb360179%28v=vs.100%29.aspx. This means all the records are returned, and the `Min` will extract one from the list. Ideally, we only want to return one record from the database. I think there is room for improvement on this method. – JDandChips May 15 '15 at 09:51
  • 2
    @JDandChips you're quoting from the form of `DefaultIfEmpty` that takes an `IEnumerable`. If you called it on an `IQueryable`, such as you would have with a database operation, then it does not return a singleton sequence, but generates an appropriate `MethodCallExpression`, and so the resultant query does not require everything to be retrieved. The suggested `EnumerableExtensions` approach here does have that issue though. – Jon Hanna May 15 '15 at 10:36
  • 1
    @JonHanna Interesting, I see your point. Thanks for enlightening me! – JDandChips May 15 '15 at 10:42
62
decimal? result = (from Item itm in itemList
                  where itm.Amount != 0
                  select (decimal?)itm.Amount).Min();

Note the conversion to decimal?. You'll get an empty result if there are none (just handle that after the fact - I'm mainly illustrating how to stop the exception). I also made "non-zero" use != rather than >.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • interesting. I can't work out how this would avoid an empty list but I'll give it a try – Chris Simpson Jan 29 '10 at 22:29
  • 7
    Try it: `decimal? result = (new decimal?[0]).Min();` gives `null` – Marc Gravell Jan 29 '10 at 22:30
  • 2
    and perhaps then use ?? 0 to get the desired result? – Christoffer Lette Jan 29 '10 at 22:34
  • It definitely works. I've just built a unit test to try it out, but I'm going to have to take 5 minutes working out why the result of the select is a single null value rather than an empty list (it's possible my sql background confusing me). Thanks for this. – Chris Simpson Jan 29 '10 at 22:39
  • 1
    @Lette, if I change it to: decimal result1 = .....Min() ?? 0; this works too, so thanks for your input. – Chris Simpson Jan 29 '10 at 22:45
  • 1
    I guess that having the Min function act on a nullable type allows it to handle the case where the source has no items by returning null, which would not be possible when acting on a non-nullable type. – Jordi Feb 05 '21 at 12:10
24

The neatest in terms of just doing it once in a small amount code is, 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 obtaining the Min of that being the neatest if we want to be able to detect this empty condition.

If however you want to actually provide a 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().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 a full set of MinOrDefault whether or not you include a selector, and whether or not you specify the default.

From this point on your code is simply:

decimal result = (from Item itm in itemList
  where itm.Amount > 0
    select itm.Amount).MinOrDefault();

So, while it's not as neat to begin with, it's neater from then on.

But wait! There's more!

Let's say you use EF and want to make use of the async support. Easily 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<TResult> MinOrDefaultAsync<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector, TSource defaultValue)
{
  return source.DefaultIfEmpty(defaultValue).MinAsync(selector);
}

public static Task<TResult> MinOrDefaultAsync<TSource, TResult>(this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)
{
  return source.DefaultIfEmpty().MinAsync(selector);
}

(Note that I don't use await here; we can directly create a Task<TSource> that does what we need without it, and hence avoid the hidden complications await brings).

But wait, there's more! Let's say we're using this with IEnumerable<T> sometimes. Our approach is sub-optimal. Surely we can do better!

First, the Min defined on int?, long?, float? double? and decimal? already do what we want anyway (as Marc Gravell's answer makes use of). Similarly, we also get the behaviour we want from the Min already defined if called for any other T?. So let's do some small, and hence easily inlined, 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;
}

Now let's start with the more general case first:

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 make use of this:

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're really bullish about performance, we can optimise for certain cases, just like 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 set of Min() provided by Enumerable. This is the sort of thing where T4 templates are useful.

At the end of all that, we have just about as performant an implementation of MinOrDefault() as we could hope for, for a wide range of types. Certainly not "neat" in the face of one use for it (again, just use DefaultIfEmpty().Min()), but very much "neat" if we find ourselves using it a lot, so we have a nice library we can reuse (or indeed, paste into answers on StackOverflow…).

bobwah
  • 2,454
  • 4
  • 33
  • 49
Jon Hanna
  • 110,372
  • 10
  • 146
  • 251
1

This approach will return the single smallest Amount value from itemList. In theory this should avoid multiple round trips to the database.

decimal? result = (from Item itm in itemList
                  where itm.Amount > 0)
                 .Min(itm => (decimal?)itm.Amount);

The null reference exception is no longer caused because we are using a nullable type.

By avoiding the use of executing methods such as Any before calling Min, we should only be making one trip to the database

JDandChips
  • 9,780
  • 3
  • 30
  • 46
  • 1
    What makes you think that the use of `Select` in the accepted answer would execute the query more than once? The accepted answer would result in a single DB call. – Jon Hanna May 15 '15 at 10:27
  • You're right, `Select` is a deferred method and wouldn't cause an execution. I've removed these lies from my answer. Reference: "Pro ASP.NET MVC4" by Adam Freeman (Book) – JDandChips May 15 '15 at 10:31
  • If you want to be really bullish in making sure there's no waste, take a look at the answer I just posted. – Jon Hanna May 15 '15 at 12:14
0

If itemList is non-nullable (where DefaultIfEmpty gives 0) and you want null as a potential output value, you can use the lambda syntax as well:

decimal? result = itemList.Where(x => x.Amount != 0).Min(x => (decimal?)x);
Jason
  • 11
  • 1
0
decimal result;
try{
  result = (from Item itm in itemList
                  where itm.Amount != 0
                  select (decimal?)itm.Amount).Min();
}catch(Exception e){
  result = 0;
}
tjgersho
  • 1
  • 2
  • 3
    It's not good practice to use exceptions for logic flow, avoid if possible... especially catch (Exception e) – Mick Apr 05 '22 at 07:29