Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are implictly-typed locals always nullable? [duplicate]

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.

like image 862
Colm Bhandal Avatar asked Jun 08 '20 17:06

Colm Bhandal


People also ask

What is implicitly typed local variable?

Implicitly typed variables are those variables which are declared without specifying the . NET type explicitly. In implicitly typed variable, the type of the variable is automatically deduced at compile time by the compiler from the value used to initialize the variable.

Why do we need nullable types?

We are using nullable types when we need to represent an undefined value of an underlying type. While Boolean values can have either true or false values, a null in this case means false as there is no undefined value. When you have a database interaction, a variable value can be either undefined or missing.

Should you use nullable reference types?

You must explicitly opt in to use these features in your existing projects. That provides a migration path and preserves backwards compatibility. Nullable contexts enable fine-grained control for how the compiler interprets reference type variables. The nullable annotation context determines the compiler's behavior.

What is the purpose of nullable types in C#?

You typically use a nullable value type when you need to represent the undefined value of an underlying value type. For example, a Boolean, or bool , variable can only be either true or false .


3 Answers

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.

like image 142
Iliar Turdushev Avatar answered Sep 29 '22 07:09

Iliar Turdushev


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!

like image 39
Colm Bhandal Avatar answered Sep 29 '22 09:09

Colm Bhandal


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.

like image 34
MikeJ Avatar answered Sep 29 '22 09:09

MikeJ