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

Why does Visual Studio Type a Newly Minted Array as Nullable?

我正在编写一个具有通用类型的函数 TVal。我写了这一行:

var zeroBased = new TVal[size];

然后在 Visual Studio (VS) 中,我使用 alt+enter 将 var 替换为显式类型。这是我得到的:

TVal[]? zeroBased = new TVal[size];

我很惊讶地发现 ? 运算符,表明该类型可能是可为空的。我认为假设使用 new 创建时类型永远不会为 null 就足够安全了,并且可以刚刚完成:

TVal[] zeroBased = new TVal[size];

是否存在在 C# 中实例化新数组可以返回 null 的场景?

注意:没有 ?,代码似乎编译得很好,我只是对 VS 的建议很感兴趣...

最小可验证示例

打开 Visual Studio,与下面指定的版本相同,创建一个新项目,根据下面的 VS 项目文件内容启用可空类型,创建一个新的 class,然后弹出此函数:

public void Test<T>(int size)
{
  var tArr = new T[size];
}

select 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 版本信息(只是对这个问题很重要的部分)

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# 8 允许您根据您在项目中设置的上下文使用可空类型。您可以找到文档 Here.

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

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

我想通过添加一些链接来扩展现有答案

C# specification proposal says:

nullable implicitly typed local variables

var infers an annotated type for reference types. For instance, in var s = ""; the var is inferred as string?.

表示引用类型的var推断出可为空的引用类型。如果使用项目文件或 #nullable pragma.

启用 nullable context,则此方法有效

此行为已被讨论 in this LDM and implemented in this issue

This is a reason 用于使 var 推断出可为空的引用类型:

At this point we've seen a large amount of code that requires people spell out the type instead of using var, because code may assign null later.

我想用我自己的解释来扩展现有答案。 破解了这个问题并直达核心。这一切都归结为启用了可空性——我们为这个项目(但不是其他人,这让我失望了!)。

then added some explicit references in support of the original answer. In particular, we were pointed to a recent discussion by the C# team on Github。该文件和现有答案引用了以下内容:

At this point we've seen a large amount of code that requires people spell out the type instead of using var, because code may assign null later.

如果没有上下文,我发现这很难理解。所以这里是解释上述内容的上下文:

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#团队得出结论:

Make var have a nullable annotated type and infer the flow type as normal.

这意味着,如果您将不可为 null 的对象分配给 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 转换为显式类型后是否删除可空性取决于您的需要。

这就是我在这种情况下要做的!