Set Nullable property default value to Nothing not working as desired - vb.net

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)

Related

One line If in VB.Net does not return nullable integer

I detected a strange behavior in the on line If statement in VB.Net
If you check this code:
jdoodle.com/a/X20
Imports System
Public Class Test
Public Shared Sub Main()
Dim x as Integer?
Dim ob1 As Objeto = New Objeto()
ob1.Valor = 1
Dim obnull As Objeto = Nothing
x = If(obnull Is Nothing, Nothing, obnull.Valor)
System.Console.WriteLine(x)
If Not obnull Is Nothing Then
x = obnull.Valor
Else
x = Nothing
End If
System.Console.WriteLine(x)
End Sub
End Class
Public Class Objeto
Public Valor As Integer
End Class
It returns 0 in the x = If(obnull Is Nothing, Nothing, obnull.Valor) statement instead of a null value.
Why?
There's nothing strange about that behaviour. The If operator is effectively generic, i.e. the return type is inferred from the common type of the second and third arguments. The third argument is type Integer and Nothing can be interpreted as type Integer too so it is. Nothing as an Integer is zero so that's what you get. If you want If to return an Integer? then at least one of the arguments needs to be that type and the other must be able to be interpreted as that type.
Dim obj As Object
Dim int = 100
Dim result1 = If(obj Is Nothing, Nothing, int)
Dim result2 = If(obj Is Nothing, DirectCast(Nothing, Integer?), int)
Dim result3 = If(obj Is Nothing, Nothing, New Integer?(int))
In that code, result1 is type Integer and will be equal to zero while both result2 and result3 will be type Integer? and will both have no value.

How to set properties of value types (e.g. Point) using reflection?

We created a new Drawing.Point dynamically at runtime and it works fine. Now we want to set the properties "X" and "Y" at runtime.
We tried to do it like this:
Public Function SetObjectProperty(propertyName As String, value As Integer, refObj As Object)
Dim propertyInfo As PropertyInfo = refObj.GetType().GetProperty(propertyName)
If propertyInfo IsNot Nothing Then
propertyInfo.SetValue(refObj, value, Nothing)
Return refObj
End If
Return Nothing
End Function
But it didn't work. The properties aren't set with the values.
Did we miss anything?
The problem is that System.Drawing.Point is a value type. When you pass this value into SetValue, it is boxed. The value is changed on the boxed object but the original value is not changed. Here is a modification that does the boxing before changing the value. You will also need the ByRef parameter modifier:
Public Function SetObjectProperty(propertyName As String, value As Integer, ByRef refObj As Object)
Dim type = refObj.GetType()
Dim propertyInfo As PropertyInfo = type.GetProperty(propertyName)
If propertyInfo IsNot Nothing Then
If type.IsValueType Then
Dim boxedObj As ValueType = refObj
propertyInfo.SetValue(boxedObj, 25)
refObj = boxedObj
Else
propertyInfo.SetValue(refObj, value)
End If
Return refObj
End If
Return Nothing
End Function
You can use it as before:
Dim p As Point
SetObjectProperty("X", 25, p)
Btw, think about if you really need the return value. It does not seem to be necessary.
The VALUE must be a Drawing.Point() Type, not an Integer.
You may utilize something like
Public Function SetObjectProperty(propertyName As String, value As Point, refObj As Object)
Dim propertyInfo As PropertyInfo = refObj.GetType().GetProperty(propertyName)
If propertyInfo IsNot Nothing Then
propertyInfo.SetValue(refObj, value.X, Nothing)
Return refObj
End If
Return Nothing
Above, you can utilize value.X or even only value to get both coordinates.

Replacement for "If object = Nothing Then" in Strict Mode VB.NET

I have a function which has a selectedID parameter of type "object".
If my parameter is the default for the underlying type: i.e. Integer default is zero, I want some action to take place.
Without "Strict On", I can use:
If selectedID = Nothing Then
'Do Something
End If
Do I have to do something like:
If (TypeOf selectedID Is Integer AndAlso selectedID.Equals(0)) _
OrElse (TypeOf selectedID Is String AndAlso selectedID.Equals(Nothing)) _
OrElse .. other types go here .. Then
'Do something
End If
Or is there a simpler method that I'm missing?
I eventually implemented Neolisk's suggestion, which had the advantage of being short, all-encompassing and very re-usable:
Public Function IsDefaultObject(obj As Object) As Boolean
Return obj.Equals(GetDefaultValue(obj.GetType()))
End Function
Public Function GetDefaultValue(t As Type) As Object
If (t.IsValueType) Then Return Activator.CreateInstance(t)
Return Nothing
End Function
I originally went with the solution of creating a function IsDefaultObject(obj) which tells me if an object has had a default value assigned. I planned to add to it as more types got noticed.
Private Function IsDefaultObject(obj As Object) As Boolean
If obj Is Nothing Then Return True
If String.IsNullOrEmpty(obj.ToString()) Then Return True
If obj.Equals(0) Then Return True
If obj.Equals(New Date()) Then Return True
Return False
End Function
Of course, I could have used the solution in Hans Passant's comment:
Private Function IsDefaultObject(obj As Object) As Boolean
Return Microsoft.VisualBasic.CompilerServices.Operators.
ConditionalCompareObjectEqual(obj, Nothing, False)
End Function
You can also use a nullable type for this.
Dim selectedID As Integer? = nothing
...
if selectedID isnot nothing then
dim value as integer = selectedID.value
...
end if
Another way you can check the nullable type has been assigned a value.
if selectedID.hasValue = true then
dim value as integer = selectedID.value
...
end if

Mystery of the If() function in 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))

VB.NET Generic Function

What I want to do is, based on the type of T do different opperations. Below is a simple example of my problem.
Public Shared Function Example(Of T)() As T
Dim retval As T
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
ElseIf TypeOf retval Is Integer Then
Dim myInt As Integer = 101
retval = myInt
End If
Return retval
End Function
I get the error "Value of Type 'String' Cannot be converted to 'T'" Same with the integer part. If I cast either to an object before asigning them to retval it works but I think that would defeat my purpose and be less efficient. Any Ideas? Thanks!
It's probably a bit late, but try this:
Public Shared Function CAnyType(Of T)(ByRef UTO As Object) As T
Return CType(UTO, T)
End Function
Public Shared Function ExecuteSQLstmtScalar(Of T)(ByVal strSQL As String) As T
Dim T_ReturnValue As T
' Here we have the result of a DB query '
Dim obj As Object = "Value from DB query cmd.ExecuteScalar"
Dim strReturnValue As Object = obj.ToString();
Try
Dim tReturnType As Type = GetType(T)
If tReturnType Is GetType(String) Then
Return CAnyType(Of T)(strReturnValue)
ElseIf tReturnType Is GetType(Boolean) Then
Dim bReturnValue As Boolean = Boolean.Parse(strReturnValue)
Return CAnyType(Of T)(bReturnValue)
ElseIf tReturnType Is GetType(Integer) Then
Dim iReturnValue As Integer = Integer.Parse(strReturnValue)
Return CAnyType(Of T)(iReturnValue)
ElseIf tReturnType Is GetType(Long) Then
Dim lngReturnValue As Long = Long.Parse(strReturnValue)
Return CAnyType(Of T)(lngReturnValue)
Else
MsgBox("ExecuteSQLstmtScalar(Of T): This type is not yet defined.")
End If
Catch ex As Exception
End Try
Return Nothing
End Function
(the secrect is casting your generic result to object, then casting from type Object to template type T).
PS:
You are responsible to ensure that your code works correctly with nullable types and NOT nullable types, as well as System.DbNull.Value. For example when string is NULL and return value type is Boolean (not nullable). On a sidenote, please also note that VB Nothing is NOT equal NULL, it's equal to C#'s default(T) (e.g. System.Guid.Empty for Guid)
With a generic method, T will be of exactly one type each time. Let's say that you have code calling Example(Of Integer). Now, in your mind, replace T with Integer. The resulting method will contain these lines (amongst others).
Dim retval As Integer
If TypeOf retval Is String Then
Dim myString As String = "Hello"
retval = myString
' more code follows '
Assigning a String to an integer like that will never work. Sure, that code will also never execute, since the If-block prevents that, but the code will still not compile. (As a side not, the above code will fail to compile because the TypeOf keyword is restricted to use with reference types, but that is another story)
Typically when creating generic methods, you will want to do the same thing with whatever input you get, but in a type safe manner. If you want to have different behavior for different types of input, you are usually better off by overloading the methods instead.
retVal = (T) "Hello World!"
Do retval = Ctype(Mystring, T) or retVal = Ctype(MyInt, T)
An alternative solution is encapsulate this kind of logic in a class and use VB CallByName function:
Class Aux(Of T)
Public Value As T
Private dicc As Dictionary(Of String, Object)
Sub New()
dicc = New Dictionary(Of String, Object)
dicc.Add("system.string", "hola")
dicc.Add("system.int32", 15)
dicc.Add("system.double", 15.0)
End Sub
Public Function Test() As T
Dim typeName As String = GetType(T).ToString.ToLower
If dicc.ContainsKey(typeName) Then
CallByName(Me, "Value", CallType.Set, dicc(typeName))
End If
Return Value
End Function
Protected Overrides Sub Finalize()
MyBase.Finalize()
If Not (dicc Is Nothing) Then dicc.Clear()
dicc = Nothing
End Sub
End Class