为啥 Visual Studio 将新创建的数组键入为 Nullable?
Posted
技术标签:
【中文标题】为啥 Visual Studio 将新创建的数组键入为 Nullable?【英文标题】:Why does Visual Studio Type a Newly Minted Array as Nullable?为什么 Visual Studio 将新创建的数组键入为 Nullable? 【发布时间】:2022-01-06 02:54:40 【问题描述】:我正在编写一个泛型类型TVal
的函数。我写了这一行:
var zeroBased = new TVal[size];
然后在 Visual Studio (VS) 中,我使用 alt+enter 将 var
替换为显式类型。这是我得到的:
TVal[]? zeroBased = new TVal[size];
我惊讶地发现?
运算符,表明该类型可能为空。我认为如果使用new
创建时该类型永远不会为空,我会足够安全,并且可以这样做:
TVal[] zeroBased = new TVal[size];
是否存在在 C# 中实例化新数组可以返回 null 的情况?
注意:如果没有?
,代码似乎可以正常编译,我只是对 VS 的建议很感兴趣...
最小可验证示例
打开Visual Studio,与下面指定的版本相同,新建一个项目,根据下面的VS项目文件内容启用可空类型,新建一个类,弹出这个函数:
public void Test<T>(int size)
var tArr = new T[size];
选择var
并点击alt+enter
,然后选择用显式类型替换var
。如果行为与我经历的相同,您将得到:
public void Test<T>(int size)
T[]? tArr = new T[size];
Visual Studio 项目文件内容
我们在这个项目中使用 C# 8 并且我们启用了 Nullables:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Nullable>enable</Nullable>
<LangVersion>8.0</LangVersion>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
<TargetFramework>netstandard2.0</TargetFramework>
<OutputType>Library</OutputType>
<Version>1.0.0.9</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" />
</ItemGroup>
</Project>
Visual Studio 版本信息(只是对这个 Q 来说很重要的部分)
Microsoft Visual Studio 社区 2019 版本 16.6.1 VisualStudio.16.Release/16.6.1+30128.74 微软 .NET 框架 版本 4.7.03062
安装版本:社区
C# 工具 3.6.0-4.20251.5+910223b64f108fcf039012e0849befb46ace6e66 IDE 中使用的 C# 组件。根据您的项目类型和设置,可能会使用不同版本的编译器。
【问题讨论】:
您使用的是什么版本? (visual studio、c#、.NET 等)另外,您能否为上下文添加周围的代码?即:如果有代码,我们可以复制/粘贴到 LinqPad 以帮助找到答案 我无法复制它,得到了T[]
和提到的代码
你用非空值初始化它,但你可以稍后将它设置为空,只要编译器可以看到
【参考方案1】:
我想通过添加一些链接来扩展现有答案
C# specification proposal says:
可为空的隐式类型局部变量
var
推断引用类型的注释类型。例如,在var s = ""
;var
被推断为string?
。
这意味着引用类型的var
推断出一个可为空的引用类型。如果使用项目文件或#nullable
pragma 启用nullable context
,则此方法有效。
此行为已在in this LDM 中讨论并在此issue 中实施。
This is a reason 用于使var
推断一个可为空的引用类型:
此时,我们已经看到了大量需要人员的代码 拼出类型而不是使用 var,因为代码可能会分配 null 稍后。
【讨论】:
【参考方案2】:我想用我自己的解释来扩展现有的答案。 MikeJ's answer 破解了这个问题并深入到了它的核心。这一切都归结为启用了可空性——我们为这个项目启用了这一点(但其他项目没有,这让我很失望!)。
Iliar Turdushev's answer 然后添加了一些明确的引用来支持原始答案。特别是,我们被指向recent discussion by the C# team on Github。该文件以及现有的答案引用它说如下:
此时我们已经看到大量代码需要人们拼出类型而不是使用 var,因为代码可能稍后会分配 null。
如果没有上下文,我发现这很难理解。所以这里是解释上述内容的上下文:
var current = myLinkedList.Head; // annotated not null
while (current is object)
...
current = current.Next; // warning, Next is annotated nullable, but current is non-null
分解一下,让我们看看第一行:
var current = myLinkedList.Head; // annotated not null
由于 Head
属性被注释为非空,编译器将 var 解释为不可空是完全可以的。然而,这种不可为空性将永远与变量保持一致,即使在程序中的某个时刻,我们想让它为空,例如在这一行:
current = current.Next; // warning, Next is annotated nullable, but current is non-null
C# 团队说,好的,我们这里有两个选择。我们可以将var
解释为始终可以为空,或者我们可以在从上下文中推断出它时将其解释为不可为空,并允许用户指定var?
以明确声明他们想要一个可以为空的类型。但我对他们文档的阅读是var?
有点违反var
的整个原则,即方便。如果我们每次使用时都必须在var
末尾附加一个额外的?
,那么可能是我们明确类型并停止使用var 的时候了。
因此,C# 团队得出结论:
使 var 有一个可以为空的注解类型,并照常推断流类型。
这意味着,如果您为 var
分配一个不可为空的值,您可以稍后在代码中安全地将 null 分配给同一引用,而不会收到任何警告。 Hans 也对此发表了评论。所以,例如,把它带回原来的 Q,我可以这样做:
public void Test<T>(int size)
var tArr = new T[size];
//Some code
tArr = null; //This is OK, because var is treated as T[]?, not T[]
而且我不会收到任何警告。所以 VS 在这里表现得很好——它尊重编译器的行为,将 var
视为可为空的,正如设计的那样,即使该 var 被初始化为不可为空的值。意思是,这对我来说是关键点,也是我问题的核心:
在将 var 转换为显式类型后,您作为程序员可以删除可空性,如果您愿意的话。
这正是我在这种情况下要做的!
【讨论】:
【参考方案3】:带有 C# 8 的 Visual Studio 允许您根据在项目中设置的上下文使用可为空的类型。你可以找到文档Here。
启用它的一种方法是在项目文件中使用<Nullable>enable</Nullable>
条目。如果你有,那么当你转换为显式变量时它会选择使用可空类型。
我不确定是否将相同的行为用于其他方式(例如编译指示)来启用它。我只尝试了项目文件的方法。
【讨论】:
以上是关于为啥 Visual Studio 将新创建的数组键入为 Nullable?的主要内容,如果未能解决你的问题,请参考以下文章
为啥在我将文件添加到项目时,Visual Studio 会检出 .vspscc 文件?
为啥Visual Studio在声明字符串数组列表时会抛出异常
将新的 MS C++ 编译器与旧的 Visual Studio 一起使用