Search code examples
c#nullable

C# nullability analysis - inconsistent tooltip


I've found a strange inconsistency between the nullability of a variable in the hover-over tooltip in Visual Studio / sharplab.io and the compiler's handling of the variable re nullability.

I wanted to check if I'm missing something or if this is maybe a bug...

Live link: sharplab.io

#nullable enable

using System;
using System.Collections.Generic;
using System.Linq;

public sealed class RectangleInfo
{
}

public sealed class PreviewLayout
{
    public sealed class Builder
    {
        public Builder()
        {
            this.Screens = new();
        }
        public List<RectangleInfo> Screens
        {
            get;
            set;
        }
        public PreviewLayout Build()
        {
            return new PreviewLayout();
        }
    }
}

public static class LayoutHelper
{
     public static PreviewLayout GetPreviewLayout()
     {
         var builder = new PreviewLayout.Builder(); 
         var screen = builder.Screens.Single();
         Console.WriteLine(screen.GetType().FullName);
         return builder.Build();
     }
}

If you hover the mouse over the screen in var screen = builder.Screens.Single(); you'll see this tooltip:

enter image description here

(local variable) RectangleInfo? screen

(note that RectangleInfo? is nullable, which is strange because Single() definitely returns a RectangleInfo in this case and there's no other nullability warnings / errors)

but in the next line screen is being treated as "not null" because this gives no Dereference of a possibly null reference. warning:

screen.GetType().FullName

If you replace var in var screen = ... with RectangleInfo the tooltip reports:

(local variable) RectangleInfo? screen

I get the same behaviour in Visual Studio, so presumably this is something the C# compiler / Roslyn is doing.

Have I missed something that means screen should legitimately be a nullable RectangleInfo??


Solution

  • When you use var in the enabled nullable aware context and the type of an initialization expression is a reference type, the compiler always infers a nullable reference type even if the type of an initialization expression isn't nullable.

    https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/declarations#implicitly-typed-local-variables

    Suppose you did this:

    var screen = builder.Screens.Single();
    if(...something...)
    {
        screen = null;
    }
    

    The C# designers determined you wouldn't want the compiler to warn you about screen = null because you never really said it had to be not-null in the first place. Fortunately, just because the variable is declared as nullable doesn't mean that it has to warn you about dereferencing: you could do this and it would work just fine, because the compiler knows that screen is not null in context:

         Screen? screen = builder.Screens.Single();
         Console.WriteLine(screen.GetType().FullName);