为什么在空引用列表上调用 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);
考虑以下因素:
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);