HasValue giving value 0 instead of Nothing - vb.net

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

Related

Odd interaction of "If" and Nullable(Of Integer)

I'm using Nullable(Of Integer) and have just been stung by Nothing being cast to 0. That's excatly what I don't want when using Nullable(Of Integer).
func1 below doesn't behave as I'd expect. I can get it to do my will by ammending it (see func2). But I don't see why this should be necessary (and I think I might find it hard to remember to do it).
Why isn't func1 doing what I want? I think I've encountered this before, and I'd rather not see it again.
Function func1(parameter As Integer) As Nullable(Of Integer)
Return If(parameter > 10, parameter, Nothing)
End Function
Function func2(parameter As Integer) As Nullable(Of Integer)
Return If(parameter > 10, parameter, DirectCast(Nothing, Integer?))
End Function
Sub Main
' Should be True
System.Console.WriteLine(func1(11).HasValue)
System.Console.WriteLine(func2(11).HasValue)
System.Console.WriteLine()
' Should be False
System.Console.WriteLine(func1(9).HasValue)
System.Console.WriteLine(func2(9).HasValue)
End Sub
The results I get (running this in LINQPad) are:
True
True
True
False
Facts important in your case:
Inline If method expected that both "true" and "false" expressions
must return same type.
Nothing is default value of type.
For Integer it is 0.
For reference type it is null
In first method inline If method expects that "False" expression must return an Integer, because compiler cannot decide return type based on Nothing, it will use type produced by "True" expression. So Nothing will produce default value of Integer type which is 0.
In second method both parameters have explicitly declared returned types, where Integer can be implicitly converted to the Nullable, so compiler will return Nullable as result of If method.
The key role in your problem is inline If method. Which uses Nothing as default value of Integer.
If you rewrite it as normal If .. Else then everything works without DirectCast
Private Function GetNullableInteger(parameter As Integer) As Integer?
If parameter > 10 Then
Return parameter
Else
Return Nothing
End If
End Function
To explain what is happening here, I'll start by removing the shorthand part of your code which may help with my explanation.
Integers in VB.net cannot be assigned using Null or DBNull. IT can be assigned using Nullable-of-T as you have. However, as soon as you make the object Nullable-ish, it can be evaluated to be 0.
Consider the following
dim x as Integer = nothing 'evaluates to x=0
So when your function runs, you use DirectCast() to return a nullable-ish Integer, which then evaluates to be not nullable by func2
Function func1(parameter As Integer) As Nullable(Of Integer)
Return If(parameter > 10, parameter, Nothing)
End Function
Function func2(parameter As Integer) As Nullable(Of Integer)
Return If(parameter > 10, parameter, DirectCast(Nothing, Nullable(of Integer)))
End Function
Sub Main()
' Should be True
System.Console.WriteLine(func1(11).HasValue)
System.Console.WriteLine(func2(11).HasValue)
System.Console.WriteLine()
' Should be False
System.Console.WriteLine(func1(9).HasValue)
System.Console.WriteLine(func2(9).HasValue)
Console.ReadLine()
End Sub
Here is func1 rewritten. Note that there is no need for casting.
Function func1(parameter As Integer) As Nullable(Of Integer)
Dim rv As New Nullable(Of Integer)
If parameter > 10 Then
rv = parameter
End If
Return rv
End Function
The If operator, If(foo,foo=true,foo=false), should be used sparingly because it is slower than the standard If construct.
edit: The statement about the If operator is incorrect.
Thanks to Chris.

Why does Assert.AreEqual fail only in one direction with my custom type that has a widening conversion?

I'm creating a wrapped type similar to Nullable(Of T) and I'm writing some unit test to test equality. Like Nullable(Of T) I have implicit conversion between MyWrapperType(Of T) and T (both directions). Therefore, I would have expected all of the following tests in NUnit to pass:
Dim x = New MyWrapperType(Of DateTime)(Date.MaxValue)
Assert.True(Date.MaxValue = x)
Assert.True(x = Date.MaxValue)
Assert.True(Date.MaxValue.Equals(x))
Assert.True(x.Equals(Date.MaxValue))
Assert.AreEqual(x, Date.MaxValue)
Assert.AreEqual(Date.MaxValue, x)
They all do, except the last one. It tells me that:
Failed: Expected: 9999-12-31 23:59:59.999 But was: <12/31/9999 11:59:59 PM>
Here are some functions from my type that may be relevant. Note: my type has a Value property similiar to Nullable(Of T):
Public Shared Widening Operator CType(value As T) As MyWrapperType(Of T)
Return New MyWrapperType(Of T)(value)
End Operator
Public Shared Widening Operator CType(value As MyWrapperType(Of T)) As T
Return value.Value
End Operator
Public Overrides Function Equals(other As Object) As Boolean
If Me.Value Is Nothing Then Return other Is Nothing
If other Is Nothing Then Return False
Return Me.Value.Equals(other)
End Function
Public Overrides Function GetHashCode() As Integer
If Me.Value Is Nothing Then Return 0
Return Me.Value.GetHashCode()
End Function
When setting breakpoints on these methods methods for the test that fails, none of them get hit except ToString which gets called when they're formatting the error to display.
Why does this call to Assert.AreEqual only fail in one direction? Is this something wrong within nunit.framework.dll (using version 2.6.1.12217)? Or am I missing a bug in my code?
If T is a Date and you do
Return Me.Value.Equals(other)
the other is passed as Object to the Date.Equals method which looks like this:
Public Overrides Function Equals(ByVal value As Object) As Boolean
If TypeOf value Is DateTime Then
Dim time As DateTime = CDate(value)
Return (Me.InternalTicks = time.InternalTicks)
End If
Return False
End Function
And as you can see the first condition will return False.
Dim isdate As Boolean = (TypeOf CObj(New MyWrapperType(Of Date)(Date.MaxValue)) Is Date)
To ensure correct casting you can do something like this:
Public Overrides Function Equals(other As Object) As Boolean
If (TypeOf other Is MyWrapperType(Of T)) Then
Dim obj As MyWrapperType(Of T) = DirectCast(other, MyWrapperType(Of T))
'...Me.Value.Equals(obj.Value)
ElseIf (TypeOf other Is T) Then
Dim obj As T = DirectCast(other, T)
'...Me.Value.Equals(obj)
End If
Return False
End Function
Edit
If we disassemble the Assert.AreEqual method it looks like this:
Call 1 : Assert
Public Shared Sub AreEqual(ByVal expected As Object, ByVal actual As Object)
Assert.AreEqual(expected, actual, String.Empty, Nothing)
End Sub
Call 2 : Assert
Public Shared Sub AreEqual(ByVal expected As Object, ByVal actual As Object, ByVal message As String, ByVal ParamArray parameters As Object())
Assert.AreEqual(Of Object)(expected, actual, message, parameters)
End Sub
Call 3 : Assert
Public Shared Sub AreEqual(Of T)(ByVal expected As T, ByVal actual As T, ByVal message As String, ByVal ParamArray parameters As Object())
If Not Object.Equals(expected, actual) Then
Dim str As String
If (((Not actual Is Nothing) AndAlso (Not expected Is Nothing)) AndAlso Not actual.GetType.Equals(expected.GetType)) Then
str = CStr(FrameworkMessages.AreEqualDifferentTypesFailMsg(IIf((message Is Nothing), String.Empty, Assert.ReplaceNulls(message)), Assert.ReplaceNulls(expected), expected.GetType.FullName, Assert.ReplaceNulls(actual), actual.GetType.FullName))
Else
str = CStr(FrameworkMessages.AreEqualFailMsg(IIf((message Is Nothing), String.Empty, Assert.ReplaceNulls(message)), Assert.ReplaceNulls(expected), Assert.ReplaceNulls(actual)))
End If
Assert.HandleFail("Assert.AreEqual", str, parameters)
End If
End Sub
Call 4 : Object
Public Shared Function Equals(ByVal objA As Object, ByVal objB As Object) As Boolean
Return ((objA Is objB) OrElse (((Not objA Is Nothing) AndAlso (Not objB Is Nothing)) AndAlso objA.Equals(objB)))
End Function
Assert.AreEqual(x, Date.MaxValue) = True
This would end up in this:
New MyWrapperType(Of DateTime)(Date.MaxValue).Equals(Date.MaxValue)
which finally ends up calling your Equals method:
Public Overrides Function Equals(other As Object) As Boolean
If Me.Value Is Nothing Then Return other Is Nothing <- Pass, Value is Date.MaxValue, not null
If other Is Nothing Then Return False <- Pass, other is Date.MaxValue, not null
Return Me.Value.Equals(other) <- Pass, Value (Date.MaxValue) = other (Date.MaxValue)
End Function
Assert.AreEqual(Date.MaxValue, x) = False
This would end up in this:
Date.MaxValue.Equals(New MyWrapperType(Of DateTime)(Date.MaxValue))
which finally ends up calling your Date.Equals(obj As Object) method:
Public Overrides Function Equals(ByVal value As Object) As Boolean
If TypeOf value Is DateTime Then '< Fail, value is not a DateTime, it's a MyWrapperType(Of T)
Dim time As DateTime = CDate(value)
Return (Me.InternalTicks = time.InternalTicks)
End If
Return False
End Function
Here's the cause to best of my understanding based off of the answer by Bjørn-Roger Kringsjå:
When I call Assert.True(Date.MaxValue.Equals(x)) the Date.Equals(other As Date) override gets called, as well as my widening operator on my type. It appears the compiler is choosing the most specific Equals override here (the one for Date) using my implicit type conversion.
When I call Assert.AreEqual(Date.MaxValue, x), the NUnit method calls Object.Equal(a, b) which then delegates it to Date.Equals(other As Object) method. This method returns false if other is not a Date. Therefore the assert fails.
If Assert.AreEqual had an override that takes dates (or maybe even two parameters of a generic type T?), it probably would have been fine, but since the only override that matches was for objects, my type conversions weren't able to come to save the day.
There are two ways your structure type could be converted to satisfy some overload of DateTime.Equals(): the compiler can use your implicit conversion operator to yield a DateTime, or it can do a boxing conversion to Object. There are many situations where, when multiple overloads are possible, the compiler should assume they're equivalent and just pick one without squawking. Unfortunately, the overloads of Equals are not equivalent (IMHO, the methods should have different names). The first four tests work because they choose an overload which causes the argument to be converted to the type of the thing doing the comparing.
The fifth assertion should fail because it's using the other overload which does not perform type coercion. The reason it succeeds is that your Equals method fails to abide by the Equals contract. No object of any type may legitimately report itself as equal to any object which will not reciprocate. Since no DateTime will consider itself equivalent to a non-converted instance of your type, your type must not consider itself equal to a non-converted DateTime.
If you dislike having inconsistent behavior with someWrapper.Equals(someDateTime) and someWrapper.Equals((Object)someDateTime), I would suggest that the best solution may be to declare overloads of == and != for (DateTime, WrapperType) and (WrapperType, DateTime) and mark them with an [Obsolete] attribute. That would cause the compiler to squawk at any effort to directly compare instances of your wrapper type to DateTime without first converting types so they match.

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))

"Conversion from type 'DBNull' to type 'Boolean' is not valid", after checking that it's not DBNull

In my ASP.Net Web-Application, I'm getting this error:
Conversion from type 'DBNull' to type 'Boolean' is not valid.
From this function:
Namespace atc
Public Class Nil
'...
Public Shared Function Bool(ByVal Item As Object) As Boolean
Return IIf(Item IsNot Nothing AndAlso Not IsDBNull(Item), CBool(Item), False)
End Function
'...
End Class
End Namespace
As you can see, I'm explicitly checking if Item is DBNull, and if it is then I return False.
The error does not occur when Item is not DBNull, so I don't understand why this is happening.
When using IIf then all arguments get evaluated, no matter if the condition evaluates to true or false. In your case the function will return false if Item is null or DBNull, but CBool(Item) will be silently executed in the background anyway and therefore throws an exception.
In VB.NET 2008 the If keyword was added to provide a real ternary operator. Replace your IIf function call with the following:
Public Shared Function Bool(ByVal Item As Object) As Boolean
Return If(Item IsNot Nothing AndAlso Not IsDBNull(Item), CBool(Item), False)
End Function
Excerpt from MSDN:
An IIf function always evaluates all three of its arguments, whereas
an If operator that has three arguments evaluates only two of them.

VB.NET generic function for checking for a value of null

I am trying to write a generic function that will check each database parameter to see if it is null, and if so, return DBNull; if not, return the object.
So here is my function:
Public Shared Function CheckForNull(ByVal obj As Object) As Object
If obj <> Nothing Then Return obj Else Return DBNull.Value
End Function
My problem is that some of the objects I am passing to the function are Nullable. So I may pass the function a Long? or Int?, but when a nullable type is passed to the function, it is converted to its value type. So if I pass a Long? that has a value of 0, the function returns DBNull because the Long? is converted to Long and a value of 0 for a Long is equivalent to Nothing. Is there anyway to make this function work for Nullable types as well?
If not, I will just fall back to using the following statements instead of one generic funciton call:
IIf(nullableVar.HasValue, nullableVar, DBNull.Value))
and
IIf(nonNullableVar IsNot Nothing , nonNullableVar, DBNull.Value))
That is not why your logic is failing. When evaluating an expression like (x=y) or (x<>y), if either argument is Nothing, VB.Net returns FALSE. Your conditional is always falling through to the ELSE clause. So instead of:
if obj <> Nothing then
try this instead:
if obj isnot nothing then