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)
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 justTrue
andFalse
but can also contain the resultNothing
. Specifically it means thatCType(Nothing, Nullable(Of Boolean) AndAlso False
produces the valueFalse
. That means that in the case of nullable operations VB is not short circuiting when the left hand side returnsNothing
. 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 againstmap
beingNothing
in all of the branches ofAndAlso
.Sharp lab demo of the behavior
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 😃