Quiz for the day. This one highlights one of the many things I don’t like about VB. What’s the output of this program?
Class TestProgram
Private Shared Sub Main()
Console.WriteLine(CBool(Nothing))
Console.WriteLine(CBool(GetObject()))
Console.WriteLine(CBool(GetString()))
End Sub
Private Shared Function GetString() As String
Return Nothing
End Function
Private Shared Function GetObject() As Object
Return Nothing
End Function
End Class
Since it’s likely that nobody is reading this, I’ll tell you:
False
False
Unhandled Exception: System.InvalidCastException: Conversion from string "" to type 'Boolean' is not valid.
---> System.FormatException: Input string was not in a correct format.
Two developers I work with, one very experienced and one much newer, were both stuck on this kind of problem within a week of each other.
The sharp eyes out there probably caught on that the return type of the “get” function is what’s making the difference, which is true. But it’s certainly not obvious why that would be the case. After all, in all three cases you’re trying to convert null to a Boolean value. Looking at the MSIL gets us headed in the right direction:
.method public static void Main() cil managed
{
.custom instance void [mscorlib]System.STAThreadAttribute::.ctor()
.entrypoint
// Code Size: 37 byte(s)
.maxstack 8
L_0000: ldc.i4.0
L_0001: call void [mscorlib]System.Console::WriteLine(bool)
L_0006: call object VbConsoleApp.TestProgram::GetObject()
L_000b: call bool [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToBoolean(object)
L_0010: call void [mscorlib]System.Console::WriteLine(bool)
L_0015: call string VbConsoleApp.TestProgram::GetString()
L_001a: call bool [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToBoolean(string)
L_001f: call void [mscorlib]System.Console::WriteLine(bool)
L_0024: ret
}
Notice:
- The conversion of the first literal just gets compiled out.
- The second two conversions are calls to different functions.
And herein lies one of the dangers of the VB conversion operators. A subtle change in what you give it can result in very different behavior. The ToBoolean(object) function will happily tell you that null==false, but the ToBoolean(string) function will disagree with a vengeance. The exception will also make it look like you gave it the empty string, when in fact you did not.
You’d probably see this if you swapped a Hashtable for a NameValueCollection, as an example. We saw it pulling the values of some checkboxes / hidden inputs out of a Request.Form collection. The other conversion operators work basically the same way, so I’m sure you’d see similar behavior.
If you think that this is nitpicky, consider that the System.Convert.ToBoolean(object) and System.Convert.ToBoolean(string) functions have consistent behavior when given null. I don’t think it’s too much to ask that CBool be held to the same standard.
When it’s my code, I avoid this stuff like the plague. These things are Thunderdome.