为啥 Visual Studio 将新创建的数组键入为 Nullable?

Posted

技术标签:

【中文标题】为啥 Visual Studio 将新创建的数组键入为 Nullable?【英文标题】:Why does Visual Studio Type a Newly Minted Array as Nullable?为什么 Visual Studio 将新创建的数组键入为 Nullable? 【发布时间】:2020-09-27 17:38:06 【问题描述】:

我正在编写一个泛型类型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。

启用它的一种方法是在项目文件中使用&lt;Nullable&gt;enable&lt;/Nullable&gt; 条目。如果你有,那么当你转换为显式变量时它会选择使用可空类型。

我不确定是否将相同的行为用于其他方式(例如编译指示)来启用它。我只尝试了项目文件的方法。

【讨论】:

以上是关于为啥 Visual Studio 将新创建的数组键入为 Nullable?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在我将文件添加到项目时,Visual Studio 会检出 .vspscc 文件?

为啥Visual Studio在声明字符串数组列表时会抛出异常

Visual Studio 中的源文件包含

将新的 MS C++ 编译器与旧的 Visual Studio 一起使用

当我构建我的项目时,Visual Studio 2008 没有创建 .exe 文件。任何想法为啥?

为啥 MPI 在 Visual Studio 2015 中不起作用?