为什么Visual Studio将新挖掘的数组键入为可为null



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

var zeroBased = new TVal[size];

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

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

我惊讶地发现了?运算符,这表明该类型可能是可以为null的。我认为,假设使用new创建时该类型从不为null,我就足够安全了,并且本可以这样做:

TVal[] zeroBased = new TVal[size];

有没有一种情况下,在C#中实例化一个新数组可以返回null?

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

最小可验证示例

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

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.1VisualStudio.16.Release/16.6.1+30128.74Microsoft.NET Framework版本4.7.03062

安装版本:社区

C#工具3.6.0-4.20251.5+910223b64f108fcf039012e0849efb46ace6e66IDE中使用的C#组件。根据您的项目类型和设置,可能会使用不同版本的编译器。

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

C#规范提案说:

可为null的隐式类型局部变量

var为引用类型推断一个带注释的类型。例如,在var s = "";11被推断为CCD_ 12。

这意味着引用类型的var推断出一个可为null的引用类型。如果使用项目文件或#nullable杂注启用了nullable context,则此操作有效。

此LDM中讨论了这种行为,并在本期中实现了这种行为。

这就是使var推断出可为null的引用类型的原因:

在这一点上,我们看到了大量需要人员的代码拼写出类型而不是使用var,因为代码可能会指定null后来

我想用我自己的解释来扩展现有的答案。MikeJ的回答破解了这个问题,并触及了它的核心。这一切都归结为启用了可空性——这是我们在这个项目中所做的(但不是其他项目,这就是让我失望的原因!)。

伊利亚尔·图尔杜舍夫的回答随后添加了一些明确的参考,以支持最初的回答。特别是,我们提到了C#团队最近在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属性被注释为非null,因此编译器将var解释为不可为null是完全可以的。然而,这种不可为空性将永远与变量保持一致,即使在程序中的某个时刻,我们希望将其设为空,例如,在以下行中:

current = current.Next; // warning, Next is annotated nullable, but current is non-null

C#团队说,好吧,我们这里有两个选项。我们可以将var始终解释为可为null,也可以在从上下文推断出CCD_18时将其解释为不可为null。并允许用户指定var?来明确声明他们想要一个可为null的类型。但我对他们文件的解读是,var?有点违反了var的整个原则,这就是便利。如果每次使用时都必须在var的末尾附加一个?,那么可能是时候明确类型并停止使用var.了

因此,C#团队得出结论:

使var具有可为null的注释类型,并将流类型推断为正常类型。

这意味着,如果将不可为null的值分配给var,则可以在稍后的代码中将null安全地分配给同一引用,而不会收到任何警告。汉斯也对此发表了评论。所以,例如,把它带回原来的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视为可为null的,正如设计的那样,,即使该var被初始化为不可为null值。意思是,这是我的关键点,也是我问题的核心:

如果您想要的话,那么在将var转换为显式类型之后,由您作为程序员来消除null能力

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

Visual Studio和C#8允许您根据项目中设置的上下文使用可为null的类型。你可以在这里找到文档。

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

我不确定同样的行为是否会用于其他方式(例如pragmas)来启用它。我只尝试了项目文件方法。

最新更新