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

3

12

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.

Weigel answered 8/6, 2020 at 17:3 Comment(3)
What versions are you using? (visual studio, c#, .NET, etc) Also, can you please add the surrounding code for context? ie: if there was code we could copy/paste into LinqPad to help find the answerSciomancy
I can't reproduce it, got T[] with mentioned codeUnrefined
You initialize it with a non-null value, but you could set it to null later, as far as the compiler can seeUnrighteous
M
1

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.

Mirepoix answered 8/6, 2020 at 17:48 Comment(0)
C
12

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, in var s = ""; the var is inferred as string?.

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.

Clastic answered 9/6, 2020 at 3:8 Comment(0)
W
11

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!

Weigel answered 9/6, 2020 at 8:20 Comment(3)
"But my reading of their document is that var? sort of violates the whole principle of var, which is convenience." It seems more convenient to me that when assigning a variable using the 'new' keyword, we can use 'var' for the non-nullable variable definition (which should be the default) and 'var?' for the nullable case. Having to type out the whole name of the type is NOT convenient. I know it's done, but I disagree with making nullability the default assumption.Sonorant
@OverlordZurg I just ran across this issue, being new to .net core and NRT's, and this seems like a bizarre decision to me. Nothing in c# is less convenient than having to type out a long type name instead of letting the compiler infer it for you. But then you lose the benefit of the null checking entirely. I wrote this question seeking a way around this: #77842028Hideaway
@Colm Bhandal: How do "remove nullability"?Hideaway
M
1

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.

Mirepoix answered 8/6, 2020 at 17:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.