Why is Enumerable Min or Max inconsistent between collections of reference and value types?

When dealing with empty sequences, I was surprised to find out that the behavior for min or max is different depending on whether the source collection elements are of value type or of reference type:

var refCollection = new object[0];
var valCollection = new int[0];
var nullableCollection = new int?[0];

var refMin = refCollection.Min(x => x); // null
var valMin = valCollection.Min(); // InvalidOperationException
var nullableMin = nullableCollection.Min(); // null

This difference of behaviors is nicely seen on .NET Core implementation of Enumerable extensions.

This, however, is not the case when looking at Jon Skeet's MinBy extension, for example, which throws on either case as I would have expected.

Doesn't the difference in behaviors just cause confusions? Is there any benefit to returning null for collections of ref types?

Jon Skeet
people
quotationmark

It's worth bearing in mind that nullable types (both nullable value types and reference types) behave differently to non-nullable value types in general when it comes to Min: a null value is treated as "missing" in general, so it's not unreasonable for the result of "the minimum of only missing values" to be "the missing value".

For example:

int?[] nullableInts = new int?[5]; // All values null
int? min = nullableInts.Min(); // No exception, min is null
nullableInts[3] = 2;
min = nullableInts.Min(); // No exception, min is 2

For non-nullable value types, there really isn't the option of indicating a "missing value" (unless the return type were changed to always be a nullable type), hence the exception... but this is reasonably easy to spot anyway, as for a non-nullable value type, the only situation where there isn't a minimum is when the source is empty.

(It's possible that MinBy should actually behave the same way :)

This is also consistent with the conversions in LINQ to XML:

XElement element = null;
int? x = (int?) element; // null
int y = (int) element; // Bang

Basically, it does make a certain amount of sense - no option would be consistent with everything.

people

See more on this question at Stackoverflow