为什么在空的引用列表上调用Min()不会引发

本文关键字:Min 调用 引用 列表 为什么 | 更新日期: 2023-09-27 18:28:30

考虑以下内容:

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

这不会抛出并返回null。就好像第二种情况调用了default运算符,但第一种情况没有调用。它也适用于可为null的类型(例如int?,即使它们是值类型)。

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

为什么在空的引用列表上调用Min()不会引发

这是我们必须询问BCL作者的设计决策。

Min扩展方法存在各种重载。对于允许null的类型,我相信Min在搜索最小值时会跳过所有null值。这适用于引用类型和类型Nullable<>(在您的示例中为Nullable<int>),后者不是引用类型,但允许nullMin方法决定忽略)。

因此,对于不可为null的结构,Min的作者可能认为返回default(TSource)是"危险的",因为在其他情况下(通常是数字0),这可能是一个有意义的全(即非null)最小值,因此输出可能会被误解。

对于允许null的类型,因为作者选择跳过从源中产生的null值,所以可以放心地假设,只有当序列只包含null值时(包括空源的情况),才返回null


请注意,在可为null的类型或引用类型的标准IComparer<>下,null值小于任何非null值。对于排序算法(如List<>.Sort使用的排序算法),顺序必须是完全的和可传递的。因此我们得到:

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