How to handle nulls in LINQ when using Min or Max?
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:
- When result is
null
then the list had no elements - 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.
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, 2022Comments
-
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):
-
Ritch Melton about 12 years..what if the default value is null?
-
Kendall Frey about 12 years@RitchMelton: I believe Min will return null.
-
Ritch Melton about 12 yearsI'm pretty sure that calling
o.Result
with a null element causes an exception to be thrown. -
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 about 12 yearstoo clumsy. i use same method as Adrian Iftode suggested - cast selected field as nullable
-
Rudey over 3 yearsI'd like to add the following: if your property is nullable and the source collection can contain
null
values, getting anull
result can mean two things: either the collection had no elements, or all values for the given property werenull
.