为什么为了让编译器通过,必须先将其强制转换为object,但编译器又说转换为object是不必要的?

编程语言 2026-07-10

下面是一个最小可复现性案例(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' 不匹配

证明:

enter image description here

所谓“那也不起作用”的意思是,我不再得到上面的错误,但我得到的是:

类型转换是冗余的

证明:

enter image description here

请注意,执行以下操作:

List<string> ys = (List<string>)xs

的结果与

List<string> ys = xs;

相同

在这种情况下,应该如何恰当地处理?显然我可以创建一个新的变量List 并用for循环将其填充,但那样会在内存占用和运行时成本方面付出很大代价。而我目前所做的除了会带来那些恼人的警告,可能会让同事和未来的自己感到难以处理和沮丧外,并不会在这些方面付出太大代价。

这些方案都让人不满意。

(我在运行.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 列表设为非空的开始。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章