为什么 Linq-to-Objects 中一个 nullable 序列的 sum 值本身也是 nullable 的?

13
作为惯例,int? 表示 System.Nullable<int>(或 System.Nullable`1[System.Int32])。
假设你有一个内存中的 IEnumerable<int?>(比如说一个 List<int?>),我们称之为 seq;那么你可以使用以下代码找到它的总和:
var seqSum = seq.Sum();

当然,这适用于扩展方法重载int? IEnumerable<int?>.Sum()(文档),实际上是System.Linq.Enumerable上的静态方法。
但是,该方法从不返回null那么为什么返回类型声明为Nullable<> 即使在seq为空集合或更一般地说是所有元素都是类型int?null值的集合的情况下,所讨论的Sum方法仍然返回零,而不是null
这是从文档中明显的,也可以从System.Core.dll源代码中看出:
public static int? Sum(this IEnumerable<int?> source) { 
    if (source == null) throw Error.ArgumentNull("source"); 
    int sum = 0; 
    checked { 
        foreach (int? v in source) { 
            if (v != null) sum += v.GetValueOrDefault(); 
        } 
    } 
    return sum; 
} 

请注意,只有一个return语句,它的表达式sum的类型为int(然后将通过包装隐式转换为int?)。
总是包装返回值似乎是浪费的。(如果需要,调用者可以在他的一侧隐式执行包装。)
此外,这种返回类型可能会导致调用者编写诸如if (!seqSum.HasValue) { /* logic to handle this */ }的代码,但实际上是无法到达的(C#编译器无法知道这个事实)。 那么为什么不将此返回参数简单地声明为没有可空性的int呢? 我想知道是否有与int? IQueryable<int?>.Sum()(在System.Linq.Queryable类中)具有相同返回类型的好处。如果有实现它的LINQ提供程序(可能是LINQ to SQL?),则后者方法在实践中可能返回null

2
我也只能想到与“Queryable”的奇偶性相同。你说它从不返回“null”,这是正确的:https://github.com/dotnet/corefx/blob/master/src/System.Linq/src/System/Linq/Sum.cs - Marc Gravell
2
更好的问题是,在检查null后为什么要使用GetValueOrDefault - juharr
2
@juharr:因为GetValueOrDefaultValue更高效,它不需要检查是否有值,只需返回字段value - Tim Schmelter
1
@juharr 在 Nullable<T> 上使用 ==null / !=null 只是语言技巧;它编译成 .HasValue 测试。 - Marc Gravell
@MarcGravell 我也是这么想的。我猜这只是个人偏好的问题。 - juharr
显示剩余15条评论
1个回答

4
几条评论提到,这个问题无法回答(或只有基于观点没有官方回复)。我不会反驳这一点。然而,人们仍然可以对可用代码进行分析并形成足够强的理论。我的理论很简单,就是这是一种现有的MS模式。
如果您查看System.Linq.Enumerable的其余部分,特别是与数学相关的函数,您将开始看到一个模式,即倾向于返回与输入参数相同的类型,除非返回值有特定的原因要是不同的类型。
请参见以下函数: Max():
public static int Max(this IEnumerable<int> source);
public static int? Max(this IEnumerable<int?> source);
public static long Max(this IEnumerable<long> source);
public static long? Max(this IEnumerable<long?> source);

Min():

public static int Min(this IEnumerable<int> source);
public static int? Min(this IEnumerable<int?> source);
public static long Min(this IEnumerable<long> source);
public static long? Min(this IEnumerable<long?> source);

Sum():

public static int Sum(this IEnumerable<int> source);
public static int? Sum(this IEnumerable<int?> source);
public static long Sum(this IEnumerable<long> source);
public static long? Sum(this IEnumerable<long?> source);

如果要违反规则,可以看一下 Average...

public static double Average(this IEnumerable<int> source);
public static double? Average(this IEnumerable<int?> source);

你可以看到,它仍然保留了Nullable<T>类型,但返回类型必须更改为适当的类型,以支持整数平均值结果。

进一步查看Average时,你会发现以下内容:

public static float Average(this IEnumerable<float> source);
public static float? Average(this IEnumerable<float?> source);

再次回到默认模式,返回与原始输入类型相同的类型。

既然我们看到了这个模式,让我们看看是否还有其他地方出现了这种模式...让我们来看看System.Math,因为我们正在谈论它。

同样,在这里我们看到了使用相同返回类型的同样模式:

public static int Abs(int value);
public static long Abs(long value);

public static int Max(int val1, int val2);
public static long Max(long val1, long val2);

我再次提醒一下,这只是一种“意见答案”。我已经寻找了任何微软最佳实践或语言规范信息,以证明这是微软支持我的分析的语言模式,但我没有找到任何相关信息。话虽如此,如果您在.NET核心库的各个位置查看,特别是System.Collections.Generic命名空间,您会发现除非有特殊原因,否则返回类型与集合类型匹配。
对于Nullable<T>类型,我认为没有理由违反这个规则。

1
我认可这一观察。可能存在倾向性,喜欢返回相同的“类型”。但请注意,像 MaxMinAverage 这样的对于空(计数为 0)源是未定义的东西,实际上在这种情况下会返回 null。但只有在可空序列的情况下才会出现这种情况;在非可空输入序列中,这些方法将在空输入序列上引发异常。 - Jeppe Stig Nielsen
这是我所期望的行为。鉴于原始的(非空)Max(等等...)在列表为空时会抛出异常。我的期望是要么像原来一样抛出异常,要么像这里一样返回一个空的 Nullable<T>。我之所以期望如此,是因为在正常情况下,对空集执行的操作没有价值,因此它们本身就是空的。Sum 有点不同,因为您从0开始,并使用集合的内容执行数学运算。如果集合为空,则仍然为0。如果集合包含一系列空值,则仍然为0。 - gmiley
换句话说,空集的最大值是多少?现在,空组合加在一起的值是多少? - gmiley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接