为什么在空引用列表上调用 Min() 不会抛出异常?

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

考虑以下因素:

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

这将抛出一个 InvalidOperationException序列不包含任何元素)。

现在让我们尝试引用类型:

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

这并没有抛出returnsnull。就好像 default 运算符被调用用于第二种情况而不是第一种情况。它也适用于可空类型(例如 int?,即使它们是值类型)。

我想知道为什么会这样,这背后是否有特定的原因?

这是一个设计决定,我们必须询问 BCL 的作者。

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<>(在您的示例中 Nullable<int>)有多种重载,它不是引用类型,但允许 null(即 Min 方法决定无视)。

所以对于 non-nullable 结构,Min 的作者可能认为它会是 "dangerous" 到 return default(TSource) 因为那可能是一个有意义的(即 non-null) 在其他情况下最小值(通常是数字 0),因此输出可能会被误解。

对于允许 null 的类型,因为作者选择跳过从源中产生的 null 值,所以可以安全地假设 null 仅被 returned如果序列仅包含 null 个值(包括空源的情况)。


请注意,根据可空类型或引用类型的标准 IComparer<>null 值小于任何 non-null 值。对于排序算法(如 List<>.Sort 使用的算法),order 必须是完整的和可传递的。因此我们得到:

  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"

Min对于真实引用类型的工作方式相同,例如System.Version(这是一个class类型):

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

您也可以考虑使用扩展程序:

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