Mystery of the If() function in VB.net - vb.net

I have this code:
dstCell.CELL_VALUE_INT = If(srcCell.CELL_VALUE_FLOAT IsNot Nothing,
Math.Round(CDbl(srcCell.CELL_VALUE_FLOAT)),
Nothing)
when srcCell.CELL_VALUE_FLOAT is Nothing it mysteriously evaluates to the True part!
Funny part is that a normal If statement correctly evaluates to the False part:
If (srcCell.CELL_VALUE_FLOAT IsNot Nothing) Then
dstCell.CELL_VALUE_INT = Math.Round(CDbl(srcCell.CELL_VALUE_FLOAT))
Else
dstCell.CELL_VALUE_INT = Nothing
End If
Any ideas?
Thank u!
EDIT:
CELL_VALUE_FLOAT is a Nullable(Of Double) and CELL_VALUE_INT is a Nullable(of Integer)
In Quickwatch the condition evaluates correclty to False, but when running the If() function evaluates to the True part.

when srcCell.CELL_VALUE_FLOAT is Nothing it mysteriously evaluates to the True part!
Nope, it does not. It just evalues the false part (Nothing) as 0, thus setting CELL_VALUE_INT to 0.
Let me elaborate: The expression
Dim i As Integer? = If(False, 1, Nothing)
fills i with 0. (Test it, if you don't believe me.)
Why does this happen? Nothing in VB.NET is not the same as null in C#. If used with a value type, Nothing means "the default value of that type". If infers Integer (not Integer?) as the common type for 1 and Nothing, and, thus, evaluates Nothing as default(Integer) = 0.
You can fix this as follows:
Dim i As Integer? = If(False, 1, DirectCast(Nothing, Integer?))
which, in your example, would mean
dstCell.CELL_VALUE_INT = If(srcCell.CELL_VALUE_FLOAT IsNot Nothing,
Math.Round(CDbl(srcCell.CELL_VALUE_FLOAT)),
DirectCast(Nothing, Integer?))
This should yield the correct value now.
Since this is quite surprising behaviour, I have filed a Microsoft Connect suggestion some time ago to add a compiler warning.

Nothing in VB.NET is not fully equal to null in C#
It is more like default(T) where T is a Type.
' VB:
dim x as DateTime = DateTime.MinValue
If x Is Nothing then
Console.WriteLine("True")
End if
' C#
var x = DateTime.MinValue
if (x == default(DateTime))
Console.WriteLine("True");
if (x == null) ' throw a compile time error
And
dim x as Double = nothing ' x will be 0 (default for Double)
the build in inline if expects both return values to be the same type. So what you a really doing is:
dstCell.CELL_VALUE_INT = If(srcCell.CELL_VALUE_FLOAT IsNot Nothing,
Math.Round(CDbl(srcCell.CELL_VALUE_FLOAT)),
Convert.ToDouble(Nothing))
since the false part gets internally converted to double
and dstCell.CELL_VALUE_INT will be 0 instead of nothing.
Try this one:
dstCell.CELL_VALUE_INT = If(srcCell.CELL_VALUE_FLOAT IsNot Nothing,
Ctype(Math.Round(CDbl(srcCell.CELL_VALUE_FLOAT)), Integer?),
Nothing)

Assuming that your value is a Single(Float):
The default value of Single is 0 not Nothing.
You can use a Nullable(Of T) if you want to check for null values.
Dim srcCell.CELL_VALUE_FLOAT As Nullable(Of Single)
srcCell_CELL.VALUE_FLOAT = Nothing
Dim dstCell.CELL_VALUE_INT As Nullable(Of Int32) = _
(If(srcCell.CELL_VALUE_FLOAT.HasValue,
CInt(Math.Round(CDbl(srcCell.CELL_VALUE_FLOAT))),
Nothing))

Related

How to affect to a boolean value the result of a boolean? expression

I would like to code in VB the equivalent of this in C#:
bool? a = whatever;
bool b= (a==true);
VB compiler does not accept this:
Dim a As Boolean?
Dim b As Boolean = (a = True)
I suppose in this context, it interprets (a = True) as an affectation while I want it to be interpreted as an expression.
(a == True) is apparently a syntax error.
You can use the GetValueOrDefault-method:
Dim a As Boolean?
Dim b As Boolean = a.GetValueOrDefault()
You could also use CBool
Dim a As Boolean?
Dim b As Boolean = CBool(a = True)
You need to be careful with the differences between 0, Nothing, and vbNull.
0 is a default value for a Boolean.
vbNull is a reserved Null value which should translate as 1.
Nothing will throw an exception in almost all circumstances.
Dim a As Boolean? = Nothing
Dim b As Boolean? = vbNull
Dim c As Boolean = vbNull
Dim d As Boolean
Print(a = True) 'will throw an Exception
Print(b = True) 'will return True (as vbNull = Int(1))
Print(c = True) 'will return True as the ? is unnecessary on a Boolean as vbNull = Int(1)
Print(d = True) 'will return False as the default value of a Boolean is 0
Print(a.GetValueOrDefault) 'will return False as this handles the Nothing case.
When working with unassigned values you should always check for Nothing first (or just follow good practice and set the value before you use it).
Dim a As Boolean?
Dim b As Boolean = IIf(IsNothing(a), False, a)
This will return False if a is Nothing, otherwise return A.
Only after testing for Nothing can you test for vbNull, as Nothing returns an error on all values. The code below will return False if Nothing or vbNull, or a otherwise.
Dim a As Boolean?
Dim b As Boolean = IIf(IsNothing(a), False, IIf(a = vbNull, False, a))
Note: you cannot use the code below as the test a = vbNull will be against Nothing which will throw an Exception.
Or(IsNothing(a), a = vbNull)
I would also avoid using GetValueOrDefault in any real application, as when you start using more complicated data types the default won't be so simple and you can get unexpected results. It's far better IMHO to test for IsNothing (or Object = Nothing, Object Is Nothing) than to rely on quirks of a datatype.
Best practice would be to ensure a has a value, which you can do using
Dim a As Boolean? = New Boolean()
Dim b As Boolean = a
The reason I say this is the best practice is because it translates to all Classes, not just Booleans. Noted that this is overkill for Booleans.
Hope this helps.

HasValue giving value 0 instead of Nothing

Question is simple, when I pass CustomClass which is Nothing into Run method at the end in Query method second.HasValue is showing 0. Shouldn't be Nothing?
Public Function Run() As Boolean
Return Query(if(CustomClass IsNot Nothing, CustomClass.Id, Nothing))
End Function
Public Function Query(second As Integer?) As Boolean
...
If second.HasValue Then
'value = 0 !
Else
'some query
End If
...
End Function
That's a VB.NET oddity. Nothing doesn't only mean null(C#) but also default(C#). So it will return the default value of a given type. You can even assign Nothing to an Integer variable(or any other reference- or value-type) for that reason.
In this case the compiler decided that Nothing means the default value of Integer which is 0. Why? Because he needs to find an implicit conversion to the Id-property which is Int32.
If you want a Nullable(Of Int32) use:
Return Query(if(CustomClass IsNot Nothing, CustomClass.Id, New Int32?()))
Because i mentioned C#, if you try the same there you will get a compiler error that there is no implicit conversion between null and int. In VB.NET there is one, the default value 0.
The reason is the inline If-statement.
It will return an Integer instead of an Integer? because CustomClass.Id apparently is of type Integer.
So you can either define CustomClass.Id as Integer? or use CType to convert it to Integer? in the inline If.
Public Function Run() As Boolean
Return Query(if(CustomClass IsNot Nothing, CType(CustomClass.Id, Integer?), Nothing))
End Function

if() operator/function bugs dealing with nothing. IF() doesn't return nothing. Why?

Most probable this is a bug reporting rather than asking question, however, if it's not so , and anyone knows why , please solve this paradox.
Function IF(arg1, arg2, arg3) isn't functioning in all circumstances as wisely described in MSDN. When arg2 is Nothing, it doesn't return Nothing in special cases, particularly, when arg1 is TRUE (arg2 is Nothing) and arg3 is not of nullable type. Instead it converts Nothing to arg3 type (sets defaults value of arg3 type for arg2) and passes that value to arg3. The beneath code illustrates that issue\question\bug
Comments imply the behavior, message titles help to follow (if sequence is lost)
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'
Dim V As Integer?
Dim D As Boolean = True
MsgBox(Nothing, vbOK, "'1st msgbox") '1st msgbox
MsgBox(If(D, Nothing, True), vbOK, "'2nd msgbox.") '2nd msgbox. If(D, Nothing, True) returns false instead of nothing
MsgBox(If(True, Nothing, True), vbOK, "3rd the same as 2nd") '3rd the same as 2nd, shows no matter either we use boolean variable or type the value directly
V = If(D, Nothing, 15)
MsgBox(V, vbOK, "4th msgbox, shows the problem isn't related to messaging the IF()...") '4th msgbox, shows the problem isn't related to messaging the IF() directly, or first, assigning it to a variable, capable of holding null
MsgBox(If(True, Nothing, 15), vbOK, "5th.") ' 5th. this returns different result (int format) but again not the expected nothing
'Now how it works
MsgBox(If(True, Nothing, "some text"), vbOK, "6th") ' 6th 'NOW this returns [Nothing] as expected, (probably) because string is nullable
MsgBox(If(True, Nothing, "some text") Is Nothing, vbOK, "7th proof of...") '7th proof of above result. Really nothing not string.empty
Dim UnevaluatedPart As Nullable(Of Integer)
MsgBox(If(True, Nothing, UnevaluatedPart), vbOK, "8th. Now it returns nothing") ' 8th. Now it returns nothing with integer? as third argument (false part)
MsgBox(If(True, Nothing, UnevaluatedPart) Is Nothing, vbOK, "' 9th. Proof of above") ' 9th. Proof of above (it's really nothing not empty string ""
End Sub
The If operator can be thought of as being like a generic function:
If(Of T)(condition As Boolean,
truePart As T,
falsePart As T) As T
That means that the second and third arguments must be the same type (either explicitly or one can be cast as the other) and the return value will be that same type. With that in mind, let's look at some of your examples.
'2nd msgbox. If(D, Nothing, True) returns false instead of nothing
MsgBox(If(D, Nothing, True), vbOK, "'2nd msgbox.")
So, for that to work at all, the second and third as the same type. What type could that possibly be? The type of the second argument is unspecified while the type of the third is Boolean. Nothing can be cast as type Boolean so that's what happens, and the return value is thus also Boolean. Casting Nothing as type Boolean yields False, so the second argument is effectively False and that's what gets returned.
Dim UnevaluatedPart As Nullable(Of Integer)
' 8th. Now it returns nothing with integer? as third argument (false part)
MsgBox(If(True, Nothing, UnevaluatedPart), vbOK, "8th. Now it returns nothing")
' 9th. Proof of above (it's really nothing not empty string ""
MsgBox(If(True, Nothing, UnevaluatedPart) Is Nothing, vbOK, "' 9th. Proof of above")
In both these cases, the type of the second parameter is unspecified and the type of the third parameter is Integer? so Nothing gets cast as type Integer?. That means that both the second and third arguments are a Integer? with no value. You're going to get the same return value whether the condition is True or False. Of course it won't be an empty String because the return value MUST be type Integer?. It's not just Nothing though; it's a Integer? with no value, so you can actually test the HasValue property of that result.
' 5th. this returns different result (int format) but again not the expected nothing
MsgBox(If(True, Nothing, 15), vbOK, "5th.")
Again, the type of the second argument is unspecified so the argument and return type is inferred from the third argument. That third argument is type Integer so that means that Nothing is cast as type Integer, which yields zero. The condition is True so it is that zero value that gets returned. If you tested whether that was equal to Nothing then you'd find that it is, which is exactly why that's what Nothing got converted to for the second parameter.
In VB.NET Nothing has two meanings:
null(as in C#)
the default value of a given type (default in C#)
If you use the conditional operator with two types both must be convertible to each other. With Nothing and True it's possible because the default value of a Boolean is False.
So it's the same as the longer version
Dim bool1 As Boolean = Nothing ' False
Dim bool2 As Boolean = If(True, bool1, True)
bool2 will be False because bool1 is False because that's the default value of Boolean.
What you learn from this? Be careful with the If operator and implicit conversions and always remember that Nothing doesn't only mean null in VB.NET.
Also remember that value types like Boolean always have a value, they are never null. In this case Nothing really means "give me the default value".
Now an exercise :) What will be the result of the integer i here?
Dim i AS int32 = If(true, nothing, 777)
"If" needs a return value, when it looks at the parameter and see Nothing with a Boolean it says "Oh, I should return a Boolean". If you want to return a nullable Boolean, then one of the parameter need to be a Boolean?. Same with integer.
Dim a As Boolean? = If(True, Nothing, True)
Dim b As Boolean? = If(False, Nothing, True)
Dim c As Boolean? = If(True, Nothing, CType(True, Boolean?))
Dim d As Boolean? = If(False, Nothing, CType(True, Boolean?))
Console.WriteLine(a) ' false
Console.WriteLine(b) ' true
Console.WriteLine(c) ' empty
Console.WriteLine(d) ' true

Store Nothing in Nullable Double wtih if statement

I just stumbled upon something really weird with nullable variables:
'Normal case
Dim myNumber As Double? = Nothing
Dim hasValue As Boolean = myNumber.HasValue 'False as expected
'Weird case
Dim myNumber2 As Double? = If(True, Nothing, 42)
Dim hasValue2 As Boolean = myNumber2.HasValue 'True ?! (myNumber2 == 0)
Why does the if store a 0 instead of Nothing in my nullable Double ?
The If(True, Nothing, 42) is what is throwing you off. For the IF statement, both results need to be the same type. Since Nothing is not a type, VB automatically looks at the second result and casts the Nothing to a double which results in 0.0.

Set Nullable property default value to Nothing not working as desired

I have a property which type is Nullable of Integer an default value Nothing as shown below:
Property TestId As Integer? = Nothing
the following code evaluates the property TestId to Nothing (as wanted)
Dim test As RadTreeNode = rtvDefinitionCreate.FindNodeByValue(DefinitionHeaderEnum.Test)
If test Is Nothing Then
definition.TestId = Nothing
Else
definition.TestId = test.Nodes(0).Value
End If
but the code below evaluates as 0 (default value for Integer, even when is Integer? with default value Nothing)
Dim test As RadTreeNode = rtvDefinitionCreate.FindNodeByValue(DefinitionHeaderEnum.Test)
definition.TestId = If(IsNothing(test), Nothing, test.Nodes(0).Value)
What is wrong with the above code? Any Help??
(later in the code when call the property, the property has 0)
It' because you are compiling you code with Option Strict Off.
If you would compile your code with Option Strict On, the compiler would give you an error, telling you that it can't convert from String to Integer?, avoiding such suprises at runtime.
This is a weirdness when using properties/ternary operator/option strict off in VB.NET.
Consider the following code:
Class Test
Property NullableProperty As Integer? = Nothing
Public NullableField As Integer? = Nothing
End Class
Sub Main()
' Setting the Property directly will lest the ternary operator evaluate to zero
Dim b = New Test() With {.NullableProperty = If(True, Nothing, "123")}
b.NullableProperty = If(True, Nothing, "123")
' Setting the Property with reflection or setting a local variable
' or a public field lets the ternary operator evaluate to Nothing
Dim localNullable As Integer? = If(True, Nothing, "123")
Dim implicitLocal = If(True, Nothing, "123")
b.NullableField = If(True, Nothing, "123")
b.GetType().GetMethod("set_NullableProperty").Invoke(b, New Object() {If(True, Nothing, "123")})
b.GetType().GetProperty("NullableProperty").SetValue(b, If(True, Nothing, "123"), Nothing)
End Sub
Another difference to consider:
Dim localNullable As Integer? = If(True, Nothing, "123")
will evaluate to Nothing but
Dim localNullable As Integer? = If(SomeNonConstantCondition, Nothing, "123")
will evaluate to 0
You can create an extension method to do the nasty work for you.
<Extension()>
Function TakeAs(Of T, R)(obj As T, selector As Func(Of T, R)) As R
If obj Is Nothing Then
Return Nothing
End If
Return selector(obj)
End Function
and call it like
definition.TestId = test.TakeAs(Of Int32?)(Function(o) o.Nodes(0).Value)