Search code examples
c#ienumerablevalue-typereference-type

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


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?


Solution

  • 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());