Updated Ternary Operator
Dictionary.com defines ternary as "consisting or involving three; threefold; triple." For VB developers, the ternary
IIf operator has provided a concise method for making conditional assignments. The
IIf operator accepts three parameters. The first parameter is a Boolean expression. When the Boolean expression evaluates to
True, the operator returns the value of the second parameter, and when it evaluates to
False the operator returns the value of the third parameter.
For example, the following
IIf operator code alternates the color string assigned to a variable. If the initial value of
strColor is "White," the operator returns the second parameter value, "Gray," Conversely, an initial value of "Gray" results in the return of the third parameter, "White."
strColor = IIf(strColor.Equals("White"), "Gray", "White").ToString()
Watch out, though; there is a potential side effect to using the
IIf operator. The standard
IIf operator always evaluates
both the possible return values. Therefore, if either the second or third parameters are function calls, they will be called and evaluated.
Take a moment to review the
ComputeOvertimePay and
ComputeRegularPay functions shown below. Both work similarly, computing regular or overtime pay based on a parameter containing the number of hours worked. If you call
ComputeOvertimePay with
intHours <= 40, it throws an exception. The
ComputeRegularPay function works similarly.
Private Function ComputeOvertimePay(ByVal intHours As Integer) As Decimal
If intHours > 40 Then
Return CDec(intHours * 1.5 * c_PayPerHour)
Else
Throw New ApplicationException("Hours too low.")
End If
End Function
Private Function ComputeRegularPay(ByVal intHours As Integer) As Decimal
If intHours <= 40 Then
Return intHours * c_PayPerHour
Else
Throw New ApplicationException("Hours too high.")
End If
End Function
Now consider the code below that uses the ternary operator to compute an employee's wages. Passing a value of 32 to both functions causes the
ComputeOvertimePay function to throw an exception.
pay = CDec(IIf( intHours > 40, _
ComputeOvertimePay(32), ComputeRegularPay(32)))
In short, the
IIf ternary operator functions similar to the more verbose code shown below. In other words, the single line of code above is the same as writing:
intHours1 = ComputeOverTimePay(32)
intHours2 = ComputeRegularPay(32)
If intHours > 40 Then
Return intHours1
Else
Return intHours2
End If
VB9 introduces a new and syntactically simplified form of the ternary operator called
If (note the loss of the extra "I") that prevents the unwanted behavior by evaluating only the appropriate expression—the other expression is never evaluated. In the code sample below, because the first expression (
intHours > 40) evaluates to
False, the new
If operator calls only the
ComputeRegularPay function.
inHours = 32
pay = CDec(If( intHours > 40, _
ComputeOvertimePay(32),
ComputeRegularPay(32)))
In other words, it functions identically to:
If intHours <= 40 Then
Return ComputeOverTimePay(32)
Else
Return ComputeRegularPay(32)
End If
Another use of the ternary
If is to replace
NULL values. In the code sample below, if the initial value of
str is
Nothing, then the
If operator returns an empty string.
str = If(str, String.Empty)
The new ternary operator (
If version) is available even if you target the 2.0 framework.
Better Nullable Types
Prior to VS2005, there was no elegant way to make a value type nullable—you needed to maintain additional state variables to keep track of whether a value type was set. The 2.0 version of the framework added the generic Nullable type, which wasn't elegant but provided a workable solution. The Nullable generic type functions as demonstrated in the code sample below.
Dim int As Integer
Dim intNullable As Nullable(Of Integer)
intNullable = Nothing
intNullable = 65
int = intNullable.Value
VB9 adds Nullable types. In VB9, to mark a type as Nullable, add a question mark after the type name in the variable declaration. As shown in the code sample below, it is used more similarly to a standard type. Nullable types are different from normal value types because they can be set to
Nothing and they expose an additional function named
HasValue that you can use to query for the Nothing-ness of the type. Note that you must perform a typecast to assign the value to a standard (non-nullable) variable.
' intNullable is a NULLable type
Dim intNullable As Integer?
Dim int As Integer
intNullable = 65
' a type cast is necessary because the types are different
int = CInt(intNullable)
intNullable = Nothing
This is a great enhancement for anyone who uses types that can be set to
NULL. It prevents the overhead and error-prone nature of using state variables to keep track of whether a value has been set, and it supplants the use of the Nullable generic type, which is not as elegant a solution as the new Nullable types.
Partial Methods
Imagine you have just created a code generation template to standardize the way you access table data. Your tool produces a set of class files that can be compiled into a data access assembly. You obviously don't want developers to recode their extensions to the generated classes each time the code is generated, so the class files produced by the tool are declared as partial classes—the class's functionality is extended by modifying another file containing the other portion of the partial class.
A further requirement of the generated classes is that an event will be fired when a field value changes. Typically, you'd handle this in older versions of VB by defining delegates to be invoked when field values change. This approach requires two things. First, the calling code, at runtime, must register methods to be called when the field value is changed. Second, the generated code needs to ensure that the calling code has in fact registered a valid method before invoking the delegate. This proves somewhat cumbersome for both the author of the generated code and the author of the calling code.
Partial methods solve these problems. Using partial methods, you would declare a stub (no code) for the partial method in the generated class as part of the code generation process. This partial method can be called from code in the generated class. Then, in the non-generated portion of the partial class, you implement only those partial methods you are interested in handling. At compile time, only calls to partial methods
with an implementation get compiled into the final assembly. The compiler removes method calls made to unimplemented partial methods, streamlining the resulting code. This is similar to the delegate solution, except that the cost of detecting an available method to call is paid at compile time instead of at runtime. In addition, the code is more readable because the partial methods solution doesn't require the various constructs to manage the delegates.
The two code samples below demonstrate the use of partial methods in a code generation environment. The first sample (from the
DataLayer_auto.vb file in the downloadable sample code) contains a partial class named "DataLayer" created by a code generator. The
Set portion of the
SSN property declaration calls the
SSNChanged partial method.
' DataLayer_auto.vb file
Partial Public Class DataLayer
. . .
Private m_strSSN As String
Public Property SSN() As String
Get
Return m_strSSN
End Get
Set(ByVal value As String)
m_strSSN = value
SSNChanged(m_strSSN)
End Set
End Property
. . .
Partial Private Sub SSNChanged(ByVal newValue As String)
End Sub
End Class
The second sample (from the
DataLayer.vb file in the downloadable code) also contains an implementation of the
SSNChanged method. It's implemented here so that it will not be overwritten when the code generation tools generates a new version of the
DataLayer_auto.vb file.
' DataLayer.vb file
Public Class DataLayer
Private Sub SSNChanged( _
ByVal newValue As String)
If newValue = "-1" Then
 | |
Figure 7. Implemented Partial Method: At this breakpoint at runtime, the IDE highlights the implemented SSNChanged method call as an executable line of code. |
m_strSSN = "000000000"
End If
End Sub
End Class
To see the
SSNChanged partial method in action, set a breakpoint on the
m_strSSN = value line in the
Set portion of the SSN property in the generated
DataLayer_auto.vb file. Run the project and click the "Partial Methods" button. When execution reaches the breakpoint, you can step into the
SSNChanged method implementation (see
Figure 7).
 | |
Figure 8. Unimplemented Partial Method: When the partial method SSNChanged is unimplemented, the IDE simply skips over the SSNChanged method call when stepping through the code. |
Now, comment out the
SSNChanged method implementation in the
DataLayer.vb file and run the project again. This time, when the IDE reaches the breakpoint, it just steps over the line that calls the
SSNChanged method, because it doesn't exist in the compiled version. You can see this contrasting behavior in Figures 5 (
SSNChanged implemented) and
Figure 8 (
SSNChanged is not implemented).
This method of extending auto-generated code turns out to be both slightly faster—and more importantly—easier to maintain than the delegate method. However, there are some restrictions to using partial methods that will limit your ability to make heavy use of them. First, they are usable only inside partial classes. Second, partial methods are required to be declared as
Private because the external interface of a class would need to change based on whether the partial methods were implemented. Because of these limitations, I cannot think of a good place to use partial methods outside of extending auto-generated code.