Why is the 2nd term in this If-AndAlso statement evaluated early? - vb.net

Edit for clarity: This is a pretty odd behaviour from the compiler, I'm asking for why it behaves this way in general, rather than how to work around it (there are several simple solutions already).
Recently, I came across a piece of code which throws contains a subtle mistake, and ends up throwing an exception. A shortened, contrived example:
Dim list As List(Of Integer) = Nothing
If list?.Any() AndAlso list.First() = 123 Then
MessageBox.Show("OK then!")
End If
In the real example, list was only occasionally Nothing, I'm just shortening the code for clarity. The intention with the first term in the If statement was to both check that list is not Nothing, and to also test for the existence of at least one element. Since in this case, list is Nothing, the list?.Any() actually / typically evaluates to Nothing. Somewhat counter-intuitively, the 2nd term, namely list.First() = 123 is also evaluated by the runtime, causing an obvious exception. This is somewhat counter-intuitive, since at first guess most people would imagine that Nothing is seem as False, and since we're using an AndAlso here, the short-circuit operator would prevent the 2nd half of the If statement from executing.
Additional investigation / "What have you tried:"
Quick check to confirm that a shortened If list?.Any() Then seems to treat list?.Any() as a False:
Dim list As List(Of Integer) = Nothing
If list?.Any() Then
MessageBox.Show("OK then!") 'This line doesn't get hit / run
End If
Also, we can work around the issue by in several ways: If list IsNot Nothing AndAlso list.Any() AndAlso list.First() = 123 Then would work just fine, as would If If(list?.Any(), False) AndAlso list.First() = 123 Then.
Since VB.Net is not my usual language, I thought I'd have a look at this in C#:
List<int> list = null;
if (list?.Any() && list.First() == 123)
{
MessageBox.Show("OK then!");
}
However, this gives a compilation error:
error CS0019: Operator '&&' cannot be applied to operands of type 'bool?' and 'bool'
Apart from the obvious fact that the stricter compiler check would prevent this mistake from being made in the C# scenario, this leads me to believe that type coercion is happening in the VB.Net scenario. One guess might be that the compiler is trying to cast the Boolean result of the 2nd term to a nullable Boolean, however this doesn't make a whole lot of sense to me. Specifically, why would it evaluate it prior to / same time as the left side, and abandoning the entire process early, like it should? Looking back at the VB.Net examples that do work correctly, all involve explicit checks which have a simple Boolean result, rather than a nullable Boolean.
My hope is that someone can give some good insight into this behaviour!

This appears to be an omission(bug) in the VB compiler's syntax evaluation. The documentation for the ?. and ?() null-conditional operators (Visual Basic) states:
Tests the value of the left-hand operand for null (Nothing) before
performing a member access (?.) or index (?()) operation; returns
Nothing if the left-hand operand evaluates to Nothing. Note that, in
the expressions that would ordinarily return value types, the
null-conditional operator returns a Nullable.
The expression list?.Any() (Enumerable.Any Method) would ordinarily return a Boolean (a ValueType), so we should expect list?.Any() to yield a Nullable(Of Boolean).
We should see a compiler error as a Nullable(Of Boolean) can not participate in an AndAlso Operator expression.
Interestingly, if we treat list?.Any() as a Nullable(Of Boolean), it is seen as documented.
If (list?.Any()).HasValue() AndAlso list.First = 123 Then
' something
End If
Edit: The above does not really address your why?.
If you de-compile the generated IL, you get something like this:
Dim source As List(Of Integer) = Nothing
Dim nullable As Boolean?
Dim nullable2 As Boolean? = nullable = If((Not source Is Nothing), New Boolean?(Enumerable.Any(Of Integer)(source)), Nothing)
nullable = If((nullable2.HasValue AndAlso Not nullable.GetValueOrDefault), False, If((Enumerable.First(Of Integer)(source) Is &H7B), nullable, False))
If nullable.GetValueOrDefault Then
MessageBox.Show("OK then!")
End If
This obviously will will not compile, but if we clean it up a bit, the source of the issue becomes apparent.
Dim list As List(Of Integer) = Nothing
Dim nullable As Boolean?
Dim nullable2 As Boolean? = If(list IsNot Nothing,
New Boolean?(Enumerable.Any(Of Integer)(list)),
Nothing)
' nullable2 is nothing, so the 3rd line below is executed and throws the NRE
nullable = If((nullable2.HasValue AndAlso Not nullable.GetValueOrDefault),
False,
If((Enumerable.First(Of Integer)(list) = 123), nullable, False))
If nullable.GetValueOrDefault Then
MessageBox.Show("OK then!")
End If
Edit2:
The OP has found the following statement from the documentation for Nullable Value Types (Visual Basic)
AndAlso and OrElse, which use short-circuit evaluation, must evaluate
their second operands when the first evaluates to Nothing.
This statement makes sense if Option Strict Off is in force and using the OrElse Operator as Nothing can implicitly be converted to False. For the OrElse operator, the second expression is not evaluated only if the first expression is True. In the case of the AndAlso operator, the second operator is not evaluated if the first expression is True.
Also, consider the following code snippet with Option Strict On.
Dim list As List(Of Integer) = Nothing
Dim booleanNullable As Nullable(Of Boolean) = list?.Any()
Dim b As Boolean = (booleanNullable AndAlso list.First() = 123)
If b Then
' do something
End If
This re-arrangement of the original logic does yield a compiler error.
With Option Strict Off, no compiler error is generated, yet the same run-time error occurs.
My Conclusion: As originally stated, this is a bug. When an AndAlso operator is included in a If-Then block the compiler treats the result of the null conditional operator using Option Strict Off type conversion relaxation regardless of the actual state of Option Strict.

Due in part to its heritage of strong support for database access, classic VB supported Null values as a first-class part of the type system. Null is included in the truth tables for all of the built-in logical operators (for example, see https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/imp-operator ).
For this reason, it is not surprising that (as you later noted) VB would choose to use similar semantics to a classic Null for a Boolean? which does not have a value.
As a practical matter, the best way to address this is probably the following:
If (list?.Any()).GetValueOrDefault() AndAlso list.First() = 123 Then
In the alternative, you could use FirstOrDefault to avoid needing the check Any first, although this does rely on the value you seek not being the default (or using a "magic" replacement value, remember that you can override the default in GetValueOrDefault), e.g.
If list?.FirstOrDefault().GetValueOrDefault() = 123 Then

Please try
if (list?.Any() IsNot Nothing AndAlso list.First() == 123)

Related

Checking if OracleParameter.Value is Null without doing ToString conversion

I have a stored procedure which has some OUT parameters. On the .NET side I'm perplexed about how to check if the parameter is null before converting it to a .NET Double?. I've done this sort of thing a lot before across several different projects and I've never encountered this issue before...
This is every combination I've tried:
Command.Parameters.Add("PparStsValue", OracleDbType.Decimal).Direction = ParameterDirection.Output
PparStsValue = If(IsDBNull(Command.Parameters("PparStsValue").Value) OrElse
IsNothing(Command.Parameters("PparStsValue").Value) OrElse
Command.Parameters("PparStsValue").Value.ToString = "",
Nothing,
CDbl(Command.Parameters("PparStsValue").Value.ToString))
There is one other thing I've done which does work, but it just seems ridiculous which is to check:
Command.Parameters("PparStsValue").Value.ToString = "null"
This evaluates to True and therefore I can identify nulls... But why does a null value convert to the string "null"? That seems so inconsistent with everything else which usually converts to "".
That aside, the main question is why don't either IsDBNull and IsNothing return true?
When using ODP your parameter does not automatically return CLR type but rather oracle type. So the way to do it for numeric OracleParameter is this
Dim oraDec as OracleDecimal =
DirectCast(Command.Parameters("PparStsValue").Value, OracleDecimal)
Dim dbl as Double
If Not oraDec.IsNull Then
dbl = Convert.ToDouble(oraDec.Value)
End If

Why in the conditionnal "if" assignement do not assign nothing/null value to a variable? [duplicate]

This question already has answers here:
Ternary operator VB vs C#: why resolves Nothing to zero? [duplicate]
(7 answers)
Closed 3 years ago.
I was surprised when I tried to assign null value to a variable using ternary expression in vb.net. When I assign through ternary expression, it doesn't works as expected.
Dim i As Integer? = Nothing
Dim j As Integer? = Nothing
i = If(True, j, 1)
j = If(True, Nothing, 1)
After execution of this code: i is nothing but j becomes 0 (zero). Why?
What is the explanation?
Why I can"t assign directly Nothing (Null) value?
I think the important thing to understand here is Nothing in VB.Net is not the same as null in other languages, because you can still assign Nothing to value types. In many other languages, null is a reference-type construct only. If you're familiar with C#, Nothing is closer to default(T) than to null.
With that in mind, take a fresh look at this expression:
If(True, Nothing, 1)
The compiler evaluates the entire expression on it's own merits, knowing nothing about i or j, even though j is the target of the assignment. The expression has to be able to stand alone.
VB.Net must also determine a type to use for the expression, and it must do this at compile-time. It is not able to infer anything from the use of Nothing about needing an Integer? rather than a basic non-nullable Integer for this type, because VB.Net is perfectly happy to assign Nothing to value types. Therefore the type of the conditional expression can only be inferred from the 1 literal in the final argument, which is a plain Integer, and not Integer?.
Given that resulting type, we now must evaluate Nothing as an integer, where the result is the 0 you observed. In theory, this part is done at runtime rather than compile time, but in practice I suspect the compiler or jitter recognizes the chance to optimize things and rewrites it all down to just j = 0.
If you want to be able to assign an Integer? with a value of Nothing, do it as you did in the first example and keep a variable handy with the correct type you can use for the assignment.

Strange behaviour of the If() statement

today I stumbled upon a strange behaviour of the VB.net If() statement. Maybe you can explain why it is working like it does, or maybe you can confirm that it is a bug.
So, I have a SQL database with a table "TestTable" with an int column "NullableColumn" that can contain NULL. I'd like to read out the content of this column.
So I declare a variable of type Nullable(Of Integer) for that matter, open a SqlClient.SqlDataReader for "SELECT NullableColumn FROM TestTable" and use the following code to get the content of this column:
Dim content as Nullable(Of Integer)
...
Using reader as SqlClient.SqlDataReader = ...
content = If(reader.IsDBNull(reader.GetOrdinal("NullableColumn")), Nothing, reader.GetInt32(reader.GetOrdinal("NullableColumn")))
End Using
But after that my variable content has the value 0, not Nothing as I would have expected.
When debugging everything looks alright, so
reader.GetOrdinal("NullableColumn") delivers the correct ordinal position of this column (which is 0)
reader.IsDBNull(0) and reader.IsDBNull(reader.GetOrdinal("NullableColumn")) deliver True, since the content of this column indeed is NULL
If(1=2, Nothing, "Not Nothing") delivers the String "Not Nothing"
If(1=1, Nothing, "Not Nothing") delivers Nothing
reader.GetInt32(reader.GetOrdinal("NullableColumn")) throws an error, since NULL can't be converted to Integer
So, why does my variable has the value 0?
In VB Nothing is not the same as null. The If operator must determine the type of its result based on the arguments passed to it. Nothing, of course, has no type so the only type that the If operator can return in your code is Int32. If the IsDBNull method returns true, then the If operator returns Nothing cast as Int32. In VB, Nothing returns the default value for a type. For an Int32, the default value is 0.
From MSDN on the Nothing keyword:
Nothing represents the default value of a data type. The default value depends
on whether the variable is of a value type or of a reference type.
For non-nullable value types, Nothing in Visual Basic differs from null in C#.
In Visual Basic, if you set a variable of a non-nullable value type to Nothing,
the variable is set to the default value for its declared type. In C#, if you
assign a variable of a non-nullable value type to null, a compile-time error
occurs.
I think just a regular If would work best:
If Not reader.IsDBNull(reader.GetOrdinal("NullableColumn")) Then
content = reader.GetInt32(reader.GetOrdinal("NullableColumn"))
End If
Or to keep it shorter
If Not reader.IsDBNull(reader.GetOrdinal("NullableColumn")) Then content = reader.GetInt32(reader.GetOrdinal("NullableColumn"))
But after that my variable content has the value 0, not Nothing as I would have expected.
How do you check the content value?
First of all, you should start with content.HasValue property. It should be False for your case of Nothing and True when correct value was fetched from database.
You should also get InvalidOperationException while accessing content.Value when it hasn't got value.
Chris has given the explanation but I dislike the style of assignment he’s chosen because it splits assignment from variable declaration.
By contrast, I recommend initialising variables upon declaration. In this case it’s admittedly slightly convoluted since you need cast either operator of If to the correct type first.
Dim content = If(reader.IsDBNull(reader.GetOrdinal("NullableColumn")),
DirectCast(Nothing, Integer?),
reader.GetInt32(reader.GetOrdinal("NullableColumn")))
Actually you can also use the slightly shorter New Integer?() instead of the DirectCast.
Of course now content is declared inside the Using block – this might not be what you want but you should try to make the declaration as local as possible.
Furthermore, this code is complex and will probably be reused. I suggest creating a separate (extension) method to convert database NULL values to nullables:
<Extension> _
Public Shared Function GetNullable(Of T)(SqlClient.SqlDataReader this, String fieldName) As T?
Dim i = this.GetOrdinal(fieldName)
Return If(this.IsDBNull(i), New T?(), this.GetFieldValue(Of T)(i))
End Function
Now you can use it as follows:
Dim content = reader.GetNullable(Of Integer)("NullableColumn")

VB.NET logic order in if statement

I'm new in VB.NET, but for C, C++, C# and other languages I had some years of expericences. This problem for me is very weird because I never met it before.
I have this line of code:
If obj is Nothing Or obj.IsDisposed Then
'do some stuffs
End If
This line of code will reveal an error when obj is Nothing because obj.IsDisposed doesn't exist (no handle for it). As what I know, the first statement of Or it returns True so the result of If statement in anycase would be True.
Can anyone give me an instruction how to get rid of this (or I have to write If..Then..Else If..End If)
try OrElse, the obj.Disposed will not be evaluated when "obj is Nothing" is true
If obj is Nothing OrElse obj.IsDisposed Then
'do some stuffs
End If
You can use the OrElse Operator it will bypass the second evaluation if the first is true.
From above Link:
A logical operation is said to be short-circuiting if the compiled code can bypass the evaluation of one expression depending on the result of another expression. If the result of the first expression evaluated determines the final result of the operation, there is no need to evaluate the second expression, because it cannot change the final result. Short-circuiting can improve performance if the bypassed expression is complex, or if it involves procedure calls.
OrElse is what you need. it will only evaluate on the first evaluation as long as it is already true
If obj is Nothing OrElse obj.IsDisposed Then
'do some stuffs
End If

String <> Nothing And String <> "" Redundant in VB .NET?

I looking at some code and came acorss this:
Dim TestString As String
...
If TestString <> Nothing And TestString <> "" Then
...
EndIf
Are both conditions checking the same thing?
Thanks
Nothing would be no string at all (null in other languages), which is different than an empty string (""), which is actually a string.
However, the check should be replaced with If Not String.IsNullOrEmpty(TestString) Then, which makes it clearer what exactly you are doing.
I just played around with this some in LINQPad, and found something mildly surprising. In VB.NET:
Dim s1 as string = Nothing
Dim s2 as string = ""
Console.WriteLine(s1 is Nothing) 'True
Console.WriteLine(s2 is Nothing) 'False
Console.WriteLine(s1 = "") 'True
Console.WriteLine(s2 = "") 'True
Console.WriteLine(string.IsNullOrEmpty(s1)) 'True
Console.WriteLine(string.IsNullOrEmpty(s2)) 'True
In C#:
string s1 = null;
string s2 = "";
Console.WriteLine(s1 == null); //True
Console.WriteLine(s2 == null); //False
Console.WriteLine(s1 == ""); //False
Console.WriteLine(s2 == ""); //True
Console.WriteLine(string.IsNullOrEmpty(s1)); //True
Console.WriteLine(string.IsNullOrEmpty(s2)); //True
I wasn't quite expecting that. It appears that VB.Net treats an Nothing as an empty string. My guess is for compatibility with older versions of VB.
This reinforces all the more that you should be using String.IsNullOrEmpty for these sorts of checks, as it is more explicit what you are checking for, and works as expected.
They are checking the samething, but they MIGHT be meant to check different things.
If IsNothing(TestString) Then
And
If TestString = Nothing Then
Are different test -- the first is seldom used, because typicaly you only really want to know if it has a non-empty value. But it can be used for treating an empty string anf a null value differently in a DB, or for detecting usage of an optional parameter (both cases requiring extra work to make sure that you don't inadvertantly slip in a wrong value, so somewhat fragile).
In the example given, the test is actually a bit wordy and confusing, if that's what you want to test then
If String.IsNullOrEmpty(TestString) Then
Is the way to go about it. If that "and" was supposed to be an "or" then it might make sense to have used IsNothing(TestString).
Yes, by definition in VB.NET "" is equivalent to Nothing including for =, <> and all VB functions; unless you explicitly care about the difference, such as by checking with Is.
Of course, you'll see differences when using general .NET functions and especially methods where str.Method will fail with a Null reference exception.
BTW I would guess the excerpt in the OP is C# code (badly) converted.