Why does calling Min() on an empty list of references does not throw?
Asked Answered
R

2

5

Consider the following:

var emptyValueList = new List<int>(); //any value type (long, float, any struct ...)
var minimum = emptyValueList.Min();

This will throw an InvalidOperationException (Sequence contains no elements).

Now let's try with a reference type:

var emptyReferenceList = new List<object>(); //any ref type will do such as object ...
var minimum = emptyReferenceList.Min();

This does not throw and returns null. It's as if the default operator was called for the second case but not for the first. It works as well for nullable types (e.g. int?, even though they're value types).

I was wondering why that was the case and if there was a specific reasoning behind this?

Randazzo answered 29/12, 2015 at 9:17 Comment(2)
new List<int?>(); int? mean that it is nullable, thats the reason why it did not throw any errorSpradling
The unsatisfactory answer is, of course, that that's what those functions are documented to doBergh
W
8

That is a design decision that we will have to ask the authors of the BCL about.

There are various overloads of the Min extension method. For types that allow null, I believe Min skips all null values when searching for the minimum. This goes for both reference types and the type Nullable<> (in your example Nullable<int>) which is not a reference type, but which allows null (that the Min methods decide to disregard).

So with non-nullable structs, the authors of Min probably thought it would be "dangerous" to return default(TSource) since that could be a meaningfull (i.e. non-null) minimum in other cases (often the number 0), and hence the output could be misunderstood.

With types that allow null, because the authors chose to skip null values yielded from the source, one can safely assume that null is returned only if the sequence contains nothing but null values (including the case of en empty source).


Note that under the standard IComparer<> for nullable types or reference types, the null value is less than any non-null value. For sorting algorithms (like the one used by List<>.Sort) the order must be total and transitive. We therefore get:

  Console.WriteLine(
    Comparer<int?>.Default.Compare(null, 7)
    ); // "-1"

  Console.WriteLine(
    Nullable.Compare((int?)null, 7)
    ); // "-1"


  Console.WriteLine(
    new List<int?> { 9, null, 7, 13, }
    .OrderBy(ni => ni).First()
    ); // ""


  // 'Min' is special in that it disregards 'null' values
  Console.WriteLine(
    new List<int?> { 9, null, 7, 13, }
    .Min()
    ); // "7"

And Min works the same way for true reference types, for example System.Version (which is a class type):

  var li = new List<Version> { new Version("9.0"), null, new Version("7.0"), new Version("13.0"), };

  Console.WriteLine(li.OrderBy(ve => ve).First());
  Console.WriteLine(li.Min());
Wilfredwilfreda answered 29/12, 2015 at 9:24 Comment(1)
Interesting, didn't know that special behavior of Min (and Max I guess). Sounds like sumulating the respective sql aggregate functions.Endrin
P
0

You may also consider using an extension:

using System.Linq;
public static class EnumerableExtensions {

    public static X MinOrDefault<T, X>(this IEnumerable<T> that, Func<T, X> minXp, X defaultValue) {
        if (that.Any())
            return that.Min(minXp);
        else
            return defaultValue;
    }
}

...

var emptyValueList = new List<int>();
var minimum = emptyValueList.MinOrDefault(e => e, 0);

var emptyEmployeeList = new List<Employee>();
var minimumSalary = emptyEmployeeList.MinOrDefault(e => e.Salary, 0);
Poolroom answered 30/4, 2021 at 8:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.