Search code examples
c#pattern-matchingnullable-reference-types

Pattern Matching is not { } foo of nullable type keep showing it as nullable type


I'm a little bit confused about the "is" operator, using VS2022 and Resharper after writing this:

//signature for the record
Task<int?> getIntIdAsync()

//code inserted into some function
if(await getIntIdAsync() is not int id) 
   return false

As Resharper suggest, changing is not int id to is not { } id and hovering id will result as int, so no more nullable int?

in this case:

//signature for the record
Task<Location?> getLocationObjectAsync()

//code inserted into some function
if(await getLocationObjectAsync() is not Location loc) 
   return false

loc will be a non-nullable Location, but if i swap from is not Location loc to is not { } loc, hovering loc show's a nullable Location? object. Can someone explain me why?

Isn't is not { } meaning is not null? So if a nullable Location? is not null shouldn't be a Location object?

From here

The type pattern can be used to test values of nullable types: a value of type Nullable (or a boxed T) matches a type pattern T2 id if the value is non-null and the type of T2 is T, or some base type or interface of T. For example, in the code fragment

int? x = 3;
if (x is int v) { // code using v }

The condition of the if statement is true at runtime and the variable v holds the value 3 of type int.


Solution

  • The type of loc is Location.

    Let's consider this snippet:

    #nullable enable
    class Location {}
    
    Location? l = new Location();
    
    if(l is Location l2) Console.WriteLine(l2.GetType());
    if(l is {} l3) Console.WriteLine(l3.GetType());
    
    
    int? i = 3;
    if(i is int i2) Console.WriteLine(i2.GetType());
    if(i is {} i3) Console.WriteLine(i3.GetType());
    

    This produces:

    Submission#15+Location
    Submission#15+Location
    System.Int32
    System.Int32
    

    I suspect that Resharper sugget is (not) {} pattern because it's more general.

    Let's consider:

    #nullable enable
    using System.Runtime.CompilerServices;
    void F(object arg1, [CallerArgumentExpression("arg1")] string arg1Exp = "?") => Console.WriteLine($"'{arg1Exp}' => {arg1}");
    
    void CheckIfNull<T>(T o)
    {
        Console.WriteLine($"Value being checked is '{(o?.ToString() ?? "null")}'. Type: {typeof(T)}");
        F(o is int);    // This works only for int?/int
        F(o is {});     // This works for all types
    }
    
    
    int? i = 3;
    CheckIfNull(i);
    
    string s = "Hello";
    CheckIfNull(s);
    
    

    This prints:

    Value being checked is '3'. Type: System.Nullable`1[System.Int32]
    'o is int' => True
    'o is {}' => True
    Value being checked is 'Hello'. Type: System.String
    'o is int' => False                 <----- Oops. We just meant to check for null/not_null
    'o is {}' => True
    

    We can see that o is int for a string gives False which may not be what you want if you're just checking for null / not_null.