考虑这两种方法:
IEnumerable<T> WhereNotNull<T>(IEnumerable<T?> seq) =>
from item in seq
select item;
IEnumerable<T> WhereNotNull2<T>(IEnumerable<T?> seq) =>
seq.Select(item => item);
第一种方法,使用 SQL LINQ 语法,不会引发任何编译器警告,而第二种方法,使用方法链,会:
Nullability of reference types in value of type 'System.Collections.Generic.IEnumerable<T?>' does not match target type 'System.Collections.Generic.IEnumerable<T>'
为什么在第一种方法中丢失了可空类型信息?
编译器使用静态分析来确定变量的空状态。变量要么不为空,要么可能为空。编译器通过两种方式确定变量不为空:
- 已为变量分配了一个已知不为空的值。
- 变量已针对 null 进行检查,并且自该检查后未被修改。 这是来自microsoft
所以对于第一个函数
WhereNotNull
编译器没有发现变量 seq
有什么奇怪的,因为此时它不是静态求值的。代码
from item in seq
select item;
将进一步转换为流利的语法
seq.Select(n=> n)
.
在
WhereNotNull2
的第二个示例中,编译器可以应用空状态分析并检测 maybe-null 情况并触发 seq
可能为空的警告。
可空类型信息在第一种方法中并没有丢失,但是 SQL LINQ 语法的 from 子句中的代码自动从序列中过滤掉任何空值,有效地将 IEnumerable
另一方面,代码的第二个方法链接版本不会从序列中过滤掉任何空值,因此生成的序列仍然包含类型 T? 的可为空元素。
要使第二种方法有效,您可以添加一个 Where 子句来过滤掉空值,如下所示:
IEnumerable<T> WhereNotNull2(IEnumerable<T?> seq) =>
seq.Where(item => item.HasValue).Select(item => item.Value);
这将在将非空值投影到 IEnumerable 序列之前从序列中过滤掉任何空值。