为什么为了让编译器通过,必须先将其强制转换为object,但编译器又说转换为object是不必要的?
下面是一个最小可复现性案例(MRE),它试图把我的初始问题缩减到最小程度:
using System.Diagnostics;
List<string?> xs = new() { "a", "b" };
// normally xs would be the result of a method call that returns List<string?>,
// but for some reason, we know it's List<string>
// so we first do a sanity check
if (xs.Exists(x => x is null))
{
throw new UnreachableException("Something is broken, we expected no nulls");
}
// and we want to cast it. That doesn't work:
List<string> ys = xs;
// that doesn't work either:
List<string> zs = (List<string>)(object)xs;
所谓“那样做不起作用”的意思是,我得到的是:
引用类型的可空性在值为 'System.Collections.Generic.List
' 的表达式中与目标类型 'System.Collections.Generic.List ' 不匹配
证明:
所谓“那也不起作用”的意思是,我不再得到上面的错误,但我得到的是:
类型转换是冗余的
证明:
请注意,执行以下操作:
List<string> ys = (List<string>)xs
的结果与
List<string> ys = xs;
相同
在这种情况下,应该如何恰当地处理?显然我可以创建一个新的变量List
这些方案都让人不满意。
(我在运行.NET 10.0.104,使用JetBrains Rider 2026.1.0.1开发。环境为Windows 11。)
解决方案
你不能把 List<string?> 转换成 List<string>,也不能反过来,因为底层类型是相同的。可空性是基于编译器的推断,而不是强制类型转换,因此在这里强制转换并不相关。你只需要以一种让编译器理解它在一开始就非空的方式来赋值该值。编译器也无法理解 if (xs.Exists(x => x is null)) 的含义,因为它深深嵌套在 List 代码中。
所以你一开始就有一个 List<string?>。现在你不能让编译器假设这是一个 List<string>,因为那样就会允许你把它传给其他只需要 List<string> 的代码,而编译器无法保证:据它所见,List 内部可能正在发生任何事情,可能会导致它返回一个 null。
如果你自己确信你的 List<string> 实际上不包含空值,因此可以安全地假设为非空,请使用 后缀运算符 ! 告诉编译器让它安静。
List<string> ys = xs!;
但若可能的话,我建议你先把 xs 列表设为非空的开始。

