Replacement for "If object = Nothing Then" in Strict Mode VB.NET - 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

Related

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.

Enumerate all controls in a form (redundant)

I'm trying to enumerate all the controls in a form that satisfy a certain condition like the code beelow
Public Enum MethodSeachEnum
StartsWith = 1
EndsWith = 2
Contains = 3
End Enum
Public Function GetAllControls(Control As Control, Key As String, MethodSeach As MethodSeachEnum, ControlType As Type, Optional UseTag As Boolean = True) As IEnumerable(Of Control)
Dim controls = Control.Controls.Cast(Of Control)()
Return (controls.SelectMany(Function(ctrl) GetAllControls(ctrl, Metodo)).Concat(controls).Where(Function(c)
Select Case MethodSeach
Case MetodoSeachEnum.EndsWith
If (UseTag) Then
Return c.Tag.ToString.ToUpper.EndsWith(Key.ToUpper) And c.GetType() Is ControlType
Else
Return c.Name.ToUpper.EndsWith(Key.ToUpper) And c.GetType() Is ControlType
End If
Case MetodoSeachEnum.StartsWith
If (UseTag) Then
Return c.Tag.ToString.ToUpper.StartsWith(Key.ToUpper) And c.GetType() Is ControlType
Else
Return c.Name.ToUpper.StartsWith(Key.ToUpper) And c.GetType() Is ControlType
End If
Case MetodoSeachEnum.Contains
If (UseTag) Then
Return c.Tag.ToString.ToUpper.Contains(Key.ToUpper) And c.GetType() Is ControlType
Else
Return c.Name.ToUpper.Contains(Key.ToUpper) And c.GetType() Is ControlType
End If
Case Else
Return False
End Select
End Function))
End Function
Inside my form there is a GroupBox and inside that some TextBox. These TextBox are not returned and I'm not understanding why...
Here how I call this function
Dim ctrls = GetAllControls(FormTagliente, "txtQuote", MetodoSeachEnum.StartsWith, GetType(TextBox), False)
For Each txt As TextBox In ctrls
...
Next
There is IMHO too few information to answer your question "why that doesn't work for your specific case"
Also the GetAllControls with two argument is missing in your code maybe the problem lies there
Anyway I toyed a little with your code (but haven't tested it so it's more a POC) and here's what I got :
Enum SearchMethod
StartsWith = 1
EndsWith = 2
Contains = 3
End Enum
Function GetAllControls(Of T As Control)(ctrl As Control, key As String, method As SearchMethod, Optional useTag As Boolean = True) As IEnumerable(Of T)
' TODO validate args
Dim upperKey = key.ToUpper
Dim searchPredicates() As Func(Of String, Boolean) = {
Function(src, tgt) src.StartsWith(upperKey),
Function(src, tgt) src.EndsWith(upperKey),
Function(src, tgt) src.Contains(upperKey)
}
Dim ctrlSelector As Func(Of Control, String) = If(useTag, Function(c) c.Tag.ToString.ToUpper, Function(c) c.Name.ToUpper)
Return GetAllControlsIterator(Of T)(ctrl, ctrlSelector, searchPredicates(CInt(method) - 1))
End Function
Private Iterator Function GetAllControlsIterator(Of T As Control)(ctrl As Control, ctrlSelector As Func(Of Control, String), searchPredicate As Func(Of String, Boolean)) As IEnumerable(Of T)
For Each child In ctrl.Controls
If searchPredicate(ctrlSelector(child)) AndAlso TypeOf child Is T Then Yield DirectCast(child, T)
For Each grandChild In GetAllControlsIterator(Of T)(child, ctrlSelector, searchPredicate)
Yield DirectCast(grandChild, T)
Next
Next
End Function
The idea was to separate the "construct the criteria logic" to the actual "loop, search, yield" one, using a generic constraint to force the targetType to be a Control (and having directly the "good" return type). I also find simpler to use an Iterator block but that's more personal
Maybe that could help you solve your problem ?

SQL Data Reader - How to handle Null column values elegantly

I'm using an SQLDataReader to retrieve values from a database which may be null. I've worked out how to handle Null string values but can't get the same trick to work with integers or booleans:
Using cmd As DbCommand = store.GetStoredProcCommand("RetrievePOCO")
store.AddInParameter(cmd, "ID", DbType.Int32, ID)
Using reader As IDataReader = store.ExecuteReader(cmd)
If reader.Read() = True Then
Dim newPOCO As New POCO()
With newPOCO
'If the source column is null TryCast will return nothing without throwing an error
.StatusXML = TryCast(reader.GetString(reader.GetOrdinal("StatusXML")), String)
'How can a null integer or boolean be set elegantly?
.AppType = TryCast(reader.GetInt32(reader.GetOrdinal("AppType")), System.Nullable(Of Integer))
.Archived = TryCast(reader.GetBoolean(reader.GetOrdinal("Archived")), Boolean)
So how can a null integer or boolean be set elegantly? I've seen suggestions in C# but they don't translate correctly to VB giving a 'TryCast operand must be reference type, but integer? is a value type' compiler errors.
I use the following function in this scenario:
Public Shared Function NoNull(ByVal checkValue As Object, ByVal returnIfNull As Object) As Object
If checkValue Is DBNull.Value Then
Return returnIfNull
Else
Return checkValue
End If
End Function
Your code would look something like this:
With newPOCO
.StatusXML = NoNull(reader("StatusXML"), "")
.AppType = NoNull(reader("AppType"), -1)
.Archived = NoNull(reader("Archived"), False)
End With
Note that this function requires you to pass the value which should be used if the value is DbNUll as the second Parameter.
You can take advantage of the IsDBNull method of the SqlDataReader and use the VB.NET ternary operator to assign a default value to your poco object
.StatusXML = If(reader.IsDBNull(reader.GetOrdinal("StatusXML")), _
"",reader.GetString(reader.GetOrdinal("StatusXML")))
It is just one line, not very elegant because you need to call two times the GetOrdinal method.
Public Function NotNull(Of T)(ByVal Value As T, ByVal DefaultValue As T) As T
If Value Is Nothing OrElse IsDBNull(Value) Then
Return DefaultValue
Else
Return Value
End If
End Function

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)

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