Why does Visual Studio Type a Newly Minted Array as Nullable?
I'm writing a function with a generic type TVal
. I wrote this line:
var zeroBased = new TVal[size];
And then in Visual Studio (VS) I used alt+enter to replace var
with an explicit type. Here's what I got:
TVal[]? zeroBased = new TVal[size];
I was surprised to find the ?
operator, indicating that the type might be nullable. I thought that I'd be safe enough assuming the type is never null when created with new
, and could have just done:
TVal[] zeroBased = new TVal[size];
Is there a scenario where instantiating a new array in C# can give you back null?
Note: the code seems to compile fine without the ?
, I'm just intrigued by VS's suggestion...
Minimal Verifiable Example
Open Visual Studio, same version as specified below, create a new project, enable nullable types as per the VS Project File Contents below, create a new class, and pop in this function:
public void Test<T>(int size)
{
var tArr = new T[size];
}
The select the var
and hit alt+enter
, and choose to replace var
with explicit type. If the behaviour is the same as the one I experienced, you'll get:
public void Test<T>(int size)
{
T[]? tArr = new T[size];
}
Visual Studio Project File Contents
We're using C# 8 for this project and we've enabled 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 Version Info (just bits that seemed important to this Q)
Microsoft Visual Studio Community 2019 Version 16.6.1 VisualStudio.16.Release/16.6.1+30128.74 Microsoft .NET Framework Version 4.7.03062
Installed Version: Community
C# Tools 3.6.0-4.20251.5+910223b64f108fcf039012e0849befb46ace6e66 C# components used in the IDE. Depending on your project type and settings, a different version of the compiler may be used.
Solution 1:
I would like to extend an existing answer by adding some links
C# specification proposal says:
nullable implicitly typed local variables
var
infers an annotated type for reference types. For instance, invar s = ""
; thevar
is inferred asstring?
.
It means that var
for reference types infers a nullable reference type. This works if nullable context
is enabled either using project file or #nullable
pragma.
This behavior was discussed in this LDM and implemented in this issue.
This is a reason for making var
infer a nullable reference type:
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.
Solution 2:
I would like to extend existing answers with my own interpretation. MikeJ's answer cracked this problem and got to the heart of it. It all boils down to nullability being enabled- which we had for this project (but not others, which is what threw me off!).
Iliar Turdushev's answer 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. That document, and the existing answer, quoted it saying the following:
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.
I found this difficult to understand without the context. So here is the context that explains the above:
var current = myLinkedList.Head; // annotated not null
while (current is object)
{
...
current = current.Next; // warning, Next is annotated nullable, but current is non-null
}
Breaking it down, let's look at that first line:
var current = myLinkedList.Head; // annotated not null
Since the Head
property is annotated as not null, it would be perfectly OK for the compiler to interpret the var as non-nullable. However, this non-nullability would then stick with the variable forever, even if, at some point in the program, we wanted to make it null e.g. in this line:
current = current.Next; // warning, Next is annotated nullable, but current is non-null
The C# team said, OK, we have two options here. We can either interpret var
as nullable always, or we can interpret it as non-nullable when it's inferrible from context, and allow users to specify var?
to explicitly state that they want a nullable type. But my reading of their document is that var?
sort of violates the whole principle of var
, which is convenience. If we had to stick an extra ?
onto the end of var
every time we used it, it's probably time we just got explicit about the type and stopped using var.
Thus, the C# team conclude:
Make var have a nullable annotated type and infer the flow type as normal.
This means that if you assign a non-nullable to a var
, you can safely assign null to that same reference later on in the code without getting any warnings. Hans commented about this too. So, for example, bringing it back to the original Q, I could do:
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[]
}
And I wouldn't get any warnings. So VS is behaving properly here- it's respecting the behaviour of the compiler to treat a var
as nullable, as designed, even when that var is initialised to a non-nullable value. Meaning, and this is the key point for me and the heart of my question:
It's up to you as the programmer to remove nullability after converting a var to an explicit type, if that's what you want.
And that's just what I will do in this case!
Solution 3:
Visual Studio with C# 8 allows you to use nullable types according to the contexts you setup in your project. You can find docs Here.
One way to enable it is with a <Nullable>enable</Nullable>
entry in your project file. If you have that then it'll choose to use the nullable type when you convert to explicit variable.
I'm not sure if that same behavior would be used for the other ways - pragmas for example - to enable it. I only tried the project file method.