views:

190

answers:

2

Those of us who've worked in VB/VB.NET have seen code similar to this abomination:

Dim name As String = IIf(obj Is Nothing, "", obj.Name)

I say "abomination" for three simple reasons:

  1. IIf is a function, all of whose parameters are evaluated; hence if obj is nothing in the above call then a NullReferenceException will be thrown. This is unexpected behavior for someone who's accustomed to short-circuited ternary operators in languages like C#.
  2. Because IIf is a function, it thus incurs the overhead of a function call. Again, though this isn't a big deal, it just doesn't feel right to someone who expects for it to behave as a ternary operation intrinsic to the language.
  3. IIf is non-generic and therefore accepts parameters of type Object, which means the following call boxes (I believe) a total of three integers:

    ' boxes 2nd and 3rd arguments as well as return value '
    Dim value As Integer = IIf(condition, 1, -1)

Now, in some more recent version of VB.NET (I'm not sure what the number is), the If operator was introduced, which works exactly the same way as the IIf function but (as I understand it) without the same shortcomings. That is to say, it does provide short-circuiting and it is an intrinstic VB operation. However, I'm not sure about the last part. The MSDN documentation doesn't seem to indicate whether If boxes its arguments or not. Does anyone know?

+10  A: 

The main thing is that you correctly identified the new If as an operator rather than a function. It is also typesafe and therefore does not need boxing, and is a direct mapping to the conditional/ternary/? operator in C/C++/C#/Java/etc

Even without the new operator, you can get some improvement in VB.Net with this code:

Public Shared Function IIf(Of T)(ByVal Expression As Boolean, ByVal TruePart As T, ByVal FalsePart As T) As T
    If Expression Then Return TruePart Else Return FalsePart
End Function
Joel Coehoorn
I noticed in the blog of some developer on the VB.NET team (forget who) that they had considered doing this, actually -- providing a generic version of `IIf`. I'm glad they went the route they did, since the generic version would fix the boxing issue but would still not provide short-circuiting (or else it would be pretty confusing to have a short-circuited intrinsic language feature disguised as a generic method).
Dan Tao
+7  A: 

Joel beat me to an answer, but here is a sample program and the generated IL that demonstrates that If() passes through to the IL's underlying ternary operator without boxing.

Public Class Test
    Public Sub New()
        Dim rnd = New Random()
        Dim result As Integer = If(rnd.Next(1000) < 500, 1, -1)
        Console.WriteLine(result)
    End Sub
End Class

As you can see the IL has no 'box' statement.

.method public specialname rtspecialname instance void .ctor() cil managed
{
    .maxstack 2
    .locals init (
        [0] int32 result,
        [1] class [mscorlib]System.Random rnd)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: call instance void [mscorlib]System.Object::.ctor()
    L_0007: nop 
    L_0008: newobj instance void [mscorlib]System.Random::.ctor()
    L_000d: stloc.1 
    L_000e: ldloc.1 
    L_000f: ldc.i4 0x3e8
    L_0014: callvirt instance int32 [mscorlib]System.Random::Next(int32)
    L_0019: ldc.i4 500
    L_001e: blt.s L_0023
    L_0020: ldc.i4.m1 
    L_0021: br.s L_0024
    L_0023: ldc.i4.1 
    L_0024: stloc.0 
    L_0025: ldloc.0 
    L_0026: call void [mscorlib]System.Console::WriteLine(int32)
    L_002b: nop 
    L_002c: nop 
    L_002d: ret 
}

Given the same program but using the older IIf() function, the following IL is produced. You can see both the boxing, and the function call overhead:

.method public specialname rtspecialname instance void .ctor() cil managed
{
    .maxstack 3
    .locals init (
        [0] int32 result,
        [1] class [mscorlib]System.Random rnd)
    L_0000: nop 
    L_0001: ldarg.0 
    L_0002: call instance void [mscorlib]System.Object::.ctor()
    L_0007: nop 
    L_0008: newobj instance void [mscorlib]System.Random::.ctor()
    L_000d: stloc.1 
    L_000e: ldloc.1 
    L_000f: ldc.i4 0x3e8
    L_0014: callvirt instance int32 [mscorlib]System.Random::Next(int32)
    L_0019: ldc.i4 500
    L_001e: clt 
    L_0020: ldc.i4.1 
    L_0021: box int32
    L_0026: ldc.i4.m1 
    L_0027: box int32
    L_002c: call object [Microsoft.VisualBasic]Microsoft.VisualBasic.Interaction::IIf(bool, object, object)
    L_0031: call int32 [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.Conversions::ToInteger(object)
    L_0036: stloc.0 
    L_0037: ldloc.0 
    L_0038: call void [mscorlib]System.Console::WriteLine(int32)
    L_003d: nop 
    L_003e: nop 
    L_003f: ret 
}
magnifico
Awesome answer. Thanks!
Dan Tao