roslyn: VB.NET ".?" Syntax OR GreaterThan Breaking Change

Version Used: Somewhere between the version in msbuild 16.1 and 16.7

Steps to Reproduce:

Code example:

Sub Main()

        Dim map As Map = Nothing

        Console.WriteLine($"1 (Expect Empty): {IsHit(map, 0, 0)}")

        map = New Map() With {.Positions = Nothing}

        Console.WriteLine($"2 (Expect Empty): {IsHit(map, 0, 0)}")

        map = New Map() With {.Positions = New List(Of List(Of String))()}
        map.Positions.Add(Nothing)

        Console.WriteLine($"3 (Expect Empty): {IsHit(map, 0, 0)}")

        map = New Map() With {.Positions = New List(Of List(Of String))()}
        map.Positions.Add(New List(Of String)())
        map.Positions(0).Add(Nothing)

        Console.WriteLine($"4 (Expect Empty): {IsHit(map, 0, 0)}")

        map = New Map() With {.Positions = New List(Of List(Of String))()}
        map.Positions.Add(New List(Of String)())
        map.Positions(0).Add("")

        Console.WriteLine($"5 (Expect Miss): {IsHit(map, 0, 0)}")

        map = New Map() With {.Positions = New List(Of List(Of String))()}
        map.Positions.Add(New List(Of String)())
        map.Positions(0).Add("X")

        Console.WriteLine($"6 (Expect Hit): {IsHit(map, 0, 0)}")

        Console.ReadLine()

    End Sub

    Function IsHit(map As Map, x As Integer, y As Integer) As String

        Try
            If map?.Positions?.Count() > x AndAlso map.Positions(x)?.Count() > y AndAlso Not map.Positions(x)(y) Is Nothing Then

                If map.Positions(x)(y).Equals("X", StringComparison.InvariantCultureIgnoreCase) Then
                    Return "Hit"
                Else
                    Return "Miss"
                End If
            Else
                Return "Empty"
            End If
        Catch ex As Exception
            Return $"Error - {ex.Message}"
        End Try

    End Function

    Class Map

        Public Property Positions As List(Of List(Of String))

    End Class

Expected Behavior:

Output:

1 (Expect Empty): Empty
2 (Expect Empty): Empty
3 (Expect Empty): Empty
4 (Expect Empty): Empty
5 (Expect Miss): Miss
6 (Expect Hit): Hit

Actual Behavior:

Output:

1 (Expect Empty): Error - Object reference not set to an instance of an object.
2 (Expect Empty): Error - Object reference not set to an instance of an object.
3 (Expect Empty): Error - Object reference not set to an instance of an object.
4 (Expect Empty): Empty
5 (Expect Miss): Miss
6 (Expect Hit): Hit

When running code compiled using an older version of the compiler, the map?.Positions?.Count() > x AndAlso map.Positions(x)?.Count() > y AndAlso Not map.Positions(x)(y) Is Nothing If check will return False and the output is as expected. However, on newer versions, this same if will throw a null reference exception.

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 26 (14 by maintainers)

Most upvoted comments

Sorry missed this issue. The change which caused this difference is #38802.

The core issue here is that the result of AndAlso in VB when dealing with nullable values is not just True and False but can also contain the result Nothing. Specifically it means that CType(Nothing, Nullable(Of Boolean) AndAlso False produces the value False. That means that in the case of nullable operations VB is not short circuiting when the left hand side returns Nothing. It must evaluate the right hand side to understand the return.

This has been the design of VB since nullable values were introduced. Unfortunately an optimization bug slipped into the compiler which caused this logic to not be consistently applied to user code. As a result it was hard for customers to predict how their programs would work and hard for us to maintain the broken logic here. After several attempts to maintain the logic we decided it was better to fix the optimization bug.

This does mean that AndAlso is not necessarily short circuiting when nullable values are involved (that has been true though since nullable was added). The code in this case needs to guard against map being Nothing in all of the branches of AndAlso.

Sharp lab demo of the behavior

But the problem now is how the Equals operator works.

Will need investigation on the VB side about this. At first glance it seems wrong.

VB has supported AndAlso on nullable booleans since we introduced them: https://docs.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/nullable-value-types

Vb is not C#. Things not allowed in C# may be allowed in VB 😃