Nothing surprises me in VB.NET
Posted on Tue 27 March 2012 in Coding
I currently work in a VB.NET project with a customer, and what amazes me is that I over and over again get the feeling that the language is designed to be easy to work with, but at the same time there are so many aspects of it that behave in a, to me, completely unexpected and non-intuitive way. I’ve thought many times that I should write something about these different “aha moments” where something has baffled me, but that would easily turn into pointless ranting. Not so long ago, however, I stumbled across something that I do think is relevant to bring to the surface. So here it comes! :-)
Remember that my perspective here is that of a Java/C# programmer. If you’re a VB.NET programmer by heart, I assume you will spot my incorrect assumption pretty quickly.
Some background: In Java and C#, you can use the conditional ? :
operator for evaluating short conditional expressions. VB.NET has the
IIf function which behaves similarly, except it eagerly evaluates both
the true expression and the false expression (it’s a function after
all), which sometimes (most of the time?) is undesirable. However, VB
9 introduced the If operator, which properly only evaluates the
expression that corresponds to the conditional value.
Now, I observed the aforementioned surprising behavior after writing a
function with return type DateTime? (i.e., a nullable DateTime) in which
I conditionally returned either a DateTime value or Nothing using the If
operator. When I tried to save the resulting value in a database in the
Nothing case I got back an exception saying that the date was way out of
range! The following code snippet illustrates what I did (except the
conditional value wasn’t a boolean literal, of course):
Module Module1
Sub Main()
Console.WriteLine(SomeDate())
End Sub
Function SomeDate() As DateTime?
Return If(False, DateTime.Now, Nothing)
End Function
End Module
What does the code above print? The answer: 0001-01-01 00:00:00, which
corresponds to DateTime.MinValue. Huh? Let’s look at the corresponding
code in C#:
internal class Program {
private static void Main(string[] args) {
Console.WriteLine(SomeDate());
}
private static DateTime? SomeDate() {
return false ? DateTime.Now : (DateTime?)null;
}
}
Note that the C# compiler doesn’t allow us to just write null as the
false expression; an explicit cast to DateTime?is required. The reason
is that it cannot implicitly convert between null and DateTime (and this
is a clue to the final answer). The C# code produces an empty output,
just as expected.
So what happened here? When googling for differences in how C# and
VB.NET initialize value types, I found this StackOverflow thread,
which lead me to read more about the Nothing keyword. I learned that
for a non-nullable value type, Nothing evaluates to the default value of
the type, which happens to be DateTime.MinValue for DateTime. So that’s
what happened! But why? The answer is in the type inference taking place
for the If operator; the compiler realizes that the resulting type can
be non-nullable DateTime, since Nothing in that context will evaluate to
the default DateTime value. Next, the result is assigned to a nullable
DateTime, which is perfectly fine. In C#, as noted above and in the
MSDN link, null does not behave as Nothing and a compile-time error
would occur (which is why the cast is required).
So, the root cause was an incorrect assumption that Nothing behaves as
null, and the subsequent use of Nothing in the If operator.
One solution is to use an explicit cast, which brings us closer to the C# version of the code:
Function SomeDate() As DateTime?
Return If(False, DateTime.Now, DirectCast(Nothing, DateTime?))
End Function
IMHO this is a bit clumsy, and it makes the otherwise elegant
conditional expression read a lot less clearly. Thus, I ended up not
using the If operator at all:
Function SomeDate() As DateTime?
If False Then Return DateTime.Now
Return Nothing
End Function