How to handle nulls in LINQ when using Min or Max?

34,355

Solution 1

A short summary of the calculation of a Min

- No mediation (Exception!)

   var min = result.Partials.Where(o => o.IsPositive).Min(o => o.Result);

This is your case: if there are no matching elements, then the Min call will raise an exception (InvalidOperationException).

- With DefaultIfEmpty() -- still troublesome

 var min = result.Partials.Where(o => o.IsPositive)
                          .Select(o => o.Result)
                          .DefaultIfEmpty()
                          .Min();

DefaultIfEmpty will create an enumeration over the 0 element, when there are no elements in the list. How do you know that 0 is the Min or if 0 stands for a list with no elements?

- Nullable values; A better solution

   var min = result.Partials.Where(o => o.IsPositive)
                            .Min(o => (decimal?)o.Result);

Here Min is either null (because that's equal to default(decimal?)) or the actual Min found.

So a consumer of this result will know that:

  1. When result is null then the list had no elements
  2. When the result is a decimal value then the list had some elements and the Min of those elements is that returned value.

However, when this doesn't matter, then min.GetValueOrDefault(0) can be called.

Solution 2

You can use the DefaultIfEmpty method to ensure the collection has at least 1 item:

result.Partials.Where(o => o.IsPositive).Select(o => o.Result).DefaultIfEmpty().Min();

Solution 3

You can't use Min (or Max) if the sequence is empty. If that shouldn't be happening, you have a different issue with how you define result. Otherwise, you should check if the sequence is empty and handle appropriately, eg:

var query = result.Partials.Where(o => o.IsPositve);
min = query.Any() ? query.Min(o => o.Result) : 0; // insert a different "default" value of your choice...    

Solution 4

Yet another way to express it in LINQ is to use Aggregate:

var min = result.Partials
    .Where(o => o.IsPositive)
    .Select(o => o.Result)
    .Aggregate(0, Math.Min); // Or any other value which should be returned for empty list

Solution 5

Since LINQ lacks methods like MinOrDefault() and MaxOrDefault(), you can create them by yourself:

public static class LinqExtensions
{
    public static TProp MinOrDefault<TItem, TProp>(this IEnumerable<TItem> This, Func<TItem, TProp> selector)
    {
        if (This.Count() > 0)
        {
            return This.Min(selector);
        }
        else
        {
            return default(TProp);
        }
    }
}

Therefore, if the collection has values, the Min() is calculated, otherwise you get the property type's default value.

An example of use:

public class Model
{
    public int Result { get; set; }
}

// ...

public void SomeMethod()
{
    Console.WriteLine("Hello World");
    var filledList = new List<Model>
    {
        new Model { Result = 10 },
        new Model { Result = 9 },
    };
    var emptyList = new List<Model>();
    var minFromFilledList = filledList.MinOrDefault(o => o.Result)); // 9
    var minFromEmptyList = emptyList.MinOrDefault(o => o.Result)); // 0
}

NOTE 1: you don't need to check if the This parameter is null: the invoked Count() already checks that, and it throws the same Exception that you would throw.

NOTE 2: This solution is good only in situations where the Count() method is cheap to call. All .NET collections are fine since they are all very efficient; it could be a problem for particular customized/non-standard collections.

Share:
34,355
Ignacio Soler Garcia
Author by

Ignacio Soler Garcia

I am now acting as a delivery manager focused on the three main pillars of software creation: People, Procedures and Code working mainly with Javascript teams (React / Redux / Node) building applications 100% in the cloud with CI/CD, etc. I am open to proposals, let's talk. Previously I used to be an experienced technical leader commanding .Net technologies, passionate about Agile methodologies and a people person.

Updated on July 09, 2022

Comments

  • Ignacio Soler Garcia
    Ignacio Soler Garcia almost 2 years

    I have the following Linq query:

    result.Partials.Where(o => o.IsPositive).Min(o => o.Result)
    

    I get an exception when result.Partials.Where(o => o.IsPositive) does not contains elements. Is there an elegant way to handle this other than splitting the operation in two and checking for null? I have a class full of operations like this one.

    EDIT: The question is related with LINQ to Objects.

    This is the Exception I'm getting (translated it says: The sequence is empty):

    enter image description here

  • Ritch Melton
    Ritch Melton about 12 years
    ..what if the default value is null?
  • Kendall Frey
    Kendall Frey about 12 years
    @RitchMelton: I believe Min will return null.
  • Ritch Melton
    Ritch Melton about 12 years
    I'm pretty sure that calling o.Result with a null element causes an exception to be thrown.
  • Kendall Frey
    Kendall Frey about 12 years
    @Ritch: Yes, of course. I was reading the documentation for decimal?, instead of the generic case. Will update answer.
  • maxlego
    maxlego about 12 years
    too clumsy. i use same method as Adrian Iftode suggested - cast selected field as nullable
  • Rudey
    Rudey over 3 years
    I'd like to add the following: if your property is nullable and the source collection can contain null values, getting a null result can mean two things: either the collection had no elements, or all values for the given property were null.