A Sub statement defines the subroutine’s name. It declares the parameters that the subroutine takes as arguments and defines the parameters’ data types. Code between the Sub statement and an End Sub statement determines what the subroutine does when it runs.
The syntax for defining a subroutine is as follows:
[attribute_list] [inheritance_mode] [accessibility]
Sub subroutine_name([parameters]) [ Implements interface.subroutine ]
[ statements ]
End Sub
The following sections describe the pieces of this declaration.
The optional attribute list is a comma-separated list of attributes that apply to the subroutine. An attribute further refines the definition of a class, method, variable, or other item to give more information to the compiler and the runtime system.
Attributes are specialized and address issues that arise when you perform very specific programming tasks. For example, the Conditional attribute means the subroutine is conditional upon the definition of some compiler constant. Example program AttributeConditional uses the following code to demonstrate the Conditional attribute:
#Const DEBUG_LIST_CUSTOMERS = True
' #Const DEBUG_LIST_EMPLOYEES = True
Private Sub Form1_Load() Handles MyBase.Load
ListCustomers()
ListEmployees()
txtResults.Select(0, 0)
End Sub
<Conditional("DEBUG_LIST_CUSTOMERS")>
Private Sub ListCustomers()
txtResults.Text &= "ListCustomers" & vbCrLf
End Sub
<Conditional("DEBUG_LIST_EMPLOYEES")>
Private Sub ListEmployees()
txtResults.Text &= "ListEmployees" & vbCrLf
End Sub
The code defines the compiler constant DEBUG_LIST_CUSTOMERS. The value DEBUG_LIST_EMPLOYEES is not defined because it is commented out.
This program’s Form1_Load event handler calls subroutines ListCustomers and ListEmployees. ListCustomers is defined using the Conditional attribute with parameter DEBUG_LIST_CUSTOMERS. That tells the compiler to generate code for the routine only if DEBUG_LIST_CUSTOMERS is defined. Because that constant is defined, the compiler generates code for this subroutine.
Subroutine ListEmployees is defined using the Conditional attribute with parameter DEBUG_LIST_EMPLOYEES. Because that constant is not defined, the compiler does not generate code for this subroutine and, when Form1_Load calls it, the subroutine call is ignored.
The following text shows the output from this program:
ListCustomers
Visual Basic 2010 defines more than 400 attributes. Many have very specialized purposes that won’t interest you most of the time, so only a few of the most useful are described here. For example, the Browsable attribute determines whether a property or event should be listed in the Properties window. It is fairly general and useful, so it’s described shortly. In contrast, the System.EnterpriseServices.ApplicationQueuing attribute enables queuing for an assembly and allows it to read method calls from message queues. This attribute is useful only in very specialized circumstances, so it isn’t described here.
The following list describes some of the most useful attributes. Most of them are in the System.ComponentModel namespace. Check the online help to find the namespaces for the others and to learn about each attribute’s parameters. Even these most useful attributes are fairly specialized and advanced so you may not immediately see their usefulness. If one of them doesn’t make sense, skip it and scan the list again after you have more experience with such topics as building custom controls.
In a routine’s declaration, the inheritance_mode can be one of the values Overloads, Overrides, Overridable, NotOverridable, MustOverride, Shadows, or Shared. These values determine how a subroutine declared within a class inherits from the parent class or how it allows inheritance in derived classes. The following list explains the meanings of these keywords:
MustOverride Sub Draw()
MustOverride Sub MoveMap(X As Integer, Y As Integer)
MustOverride Sub Delete()
...
A subroutine’s accessibility clause can take one of these values: Public, Protected, Friend, Protected Friend, or Private. These values determine which pieces of code can invoke the subroutine. The following list explains these keywords:
To reduce the amount of information that developers must remember, you should generally declare subroutines with the most restricted accessibility that allows them to do their jobs. If you can, declare the subroutine Private. Then, developers working on other parts of the application don’t even need to know that the subroutine exists. They can create other routines with the same name if necessary and won’t accidentally misuse the subroutine.
Later, if you discover that you need to use the subroutine outside of its class or module, you can change its declaration to allow greater accessibility.
The subroutine’s name must be a valid Visual Basic identifier. That means it should begin with a letter or an underscore. It can then contain zero or more letters, numbers, and underscores. If the name begins with an underscore, it must include at least one other character so that Visual Basic can tell it apart from a line continuation character.
Many developers use camel case when naming subroutines so a subroutine’s name consists of several descriptive words with their first letters capitalized. A good method for generating subroutine names is to use a short phrase beginning with a verb and describing what the subroutine does. Some examples include LoadData, SaveNetworkConfiguration, and PrintExpenseReport.
Subroutine names with leading underscores can be hard to read, so you should either save them for special purposes or avoid them entirely. Names such as _1 and __ (two underscores) are particularly confusing.
The parameters section of the subroutine declaration defines the numbers and types of the parameters that the subroutine takes as arguments. This section also gives the names the subroutine will use to refer to the values.
Declaring parameters is very similar to declaring variables. See Chapter 14, “Data Types, Variables, and Constants,” for information on variable declarations, data types, and other related topics.
The following sections describe some of the more important details related to subroutine parameter declarations.
If you include the optional ByVal keyword before a parameter’s declaration, the subroutine makes its own local copy of the argument. The subroutine can modify this value all it wants and the corresponding value in the calling procedure isn’t changed.
If you declare a parameter with the ByRef keyword, the subroutine does not create a separate copy of the argument. Instead, it uses a reference to the original argument passed into the subroutine and any changes the subroutine makes to the value are reflected in the calling subroutine.
For example, consider the following code. The main program initializes the variable A and prints its value in the Output window. It then calls subroutine DisplayDouble, which doubles its parameter X and displays the new value. Because X is declared ByRef, this doubles the value of the variable A that was passed by the main program into the subroutine. When the subroutine ends and the main program resumes, it displays the new doubled value of variable A.
Private Sub Main()
Dim A As Integer = 12
Debug.WriteLine("Main: " & A)
DisplayDouble(A)
Debug.WriteLine("Main: " & A)
End Sub
Private Sub DisplayDouble(ByRef X As Integer)
X *= 2
Debug.WriteLine("DisplayDouble: " & X)
End Sub
The following shows the results:
Main: 12
DisplayDouble: 24
Main: 24
If you declare an array parameter using ByVal or ByRef, those keywords apply to the array itself, not to the array’s values. In either case, the subroutine can modify the values inside the array.
If the array is declared ByRef, the subroutine can also change the memory to which the array points. It can set the parameter to a completely new array and the calling code will see the change in the array that it passed to the subroutine.
A subroutine can fail to update a parameter declared using the ByRef keyword in a couple ways. The most confusing occurs if you enclose a variable in parentheses when you pass it to the subroutine. Parentheses tell Visual Basic to evaluate their contents as an expression. Visual Basic creates a temporary variable to hold the result of the expression and then passes the temporary variable into the procedure. If the procedure’s parameter is declared ByRef, the subroutine updates the temporary variable but not the original variable, so the calling routine doesn’t see any change to its value.
The following code calls subroutine DisplayDouble, passing it the variable A surrounded by parentheses. Subroutine DisplayDouble modifies its parameter’s value, but the result doesn’t get back to the variable A.
Private Sub Main()
Dim A As Integer = 12
Debug.WriteLine("Main: " & A)
DisplayDouble((A))
Debug.WriteLine("Main: " & A)
End Sub
Private Sub DisplayDouble(ByRef X As Integer)
X *= 2
Debug.WriteLine("DisplayDouble: " & X)
End Sub
The following text shows the results:
Main: 12
DisplayDouble: 24
Main: 12
Chapter 14 has more to say about parameters declared with the ByVal and ByRef keywords.
If you declare a parameter with the Optional keyword, the code that uses it may omit that parameter. When you declare an optional parameter, you must give it a default value for the subroutine to use if the parameter is omitted by the calling routine.
The DisplayError subroutine in the following code demonstrates an optional string parameter:
Private Sub DisplayError(Optional error_message As String =
"An error occurred")
MessageBox.Show(error_message)
End Sub
Private Sub PlaceOrder(the_customer As Customer, order_items() As OrderItem)
' See if the_customer exists.
If the_customer Is Nothing Then
DisplayError("Customer is Nothing in subroutine PlaceOrder")
Exit Sub
End If
' See if the_customer is valid.
If Not the_customer.IsValid() Then
DisplayError()
Exit Sub
End If
' Generate the order.
...
End Sub
If the calling routine provides the optional error_message parameter, the subroutine displays it. If the calling routine leaves this parameter out, DisplayError uses its default message “An error occurred.”
The PlaceOrder subroutine checks its the_customer parameter. If this parameter is Nothing, PlaceOrder calls DisplayError to show the message “Customer is Nothing in subroutine PlaceOrder.” Next, subroutine PlaceOrder calls the_customer’s IsValid function. If IsValid returns False, the subroutine calls DisplayError this time without the parameter so DisplayError presents its default message.
Optional parameters must go at the end of the parameter list, so if one parameter uses the Optional keyword, all of the following parameters must use it, too.
Public Sub Sub1(Optional ByVal x? As Integer = Nothing)
...
End Sub
Public Sub Sub2(Optional ByVal x As Integer? = Nothing)
...
End Sub
Public Sub Sub3(Optional ByVal x As Nullable(Of Integer) = 0)
...
End Sub
Optional parameters are particularly useful for initializing values in a class’s constructor. The following code shows a DrawableRectangle class. Its constructor takes as parameters the rectangle’s position and size. All the parameters are optional, so the main program can omit them if it desires. Because each parameter has default values, the constructor always knows it will have the four values, so it can always initialize the object’s Bounds variable.
Public Class DrawableRectangle
Public Bounds As Rectangle
Public Sub New(
Optional X As Integer = 0,
Optional Y As Integer = 0,
Optional Width As Integer = 100,
Optional Height As Integer = 100
)
Bounds = New Rectangle(X, Y, Width, Height)
End Sub
...
End Class
Note that overloaded subroutines cannot differ only in optional parameters. If a call to the subroutine omitted the optional parameters, Visual Basic would be unable to tell which version of the subroutine to use.
Different developers have varying opinions on whether you should use optional parameters or overloaded routines under various circumstances. For example, suppose that the FireEmployee method could take one or two parameters giving either the employee’s name or the name and reason for dismissal. You could make this a subroutine with the reason parameter optional, or you could make one overloaded version of the FireEmployee method for each possible parameter list.
One argument in favor of optional parameters is that overloaded methods might duplicate a lot of code. However, it is easy to make each version of the method call another version that allows more parameters, passing in default values. For example, in the following code the first version of the FireEmployee method simply invokes the second version:
Public Sub FireEmployee(employee_name As String)
FireEmployee(employee_name, "Unknown reason")
End Sub
Public Sub FireEmployee(employee_name As String, reason As String)
...
End Sub
Method overloading is generally superior when the different versions of the routine need to do something different. You might be able to make a single routine with optional parameters take different actions based on the values of its optional parameters, but separating the code into overloaded routines will probably produce a cleaner solution.
Sometimes it is convenient to allow a subroutine to take a variable number of parameters. For example, a subroutine might take as parameters the addresses of people who should receive e-mail. It would loop through the names to send each a message.
One approach is to include a long list of optional parameters. For example, the e-mail subroutine might set the default value for each of its parameters to an empty string. Then it would need to send e-mail to every address parameter that was not empty.
Unfortunately, this type of subroutine would need to include code to deal with each optional parameter separately. This would also place an upper limit on the number of parameters the subroutine can take (however many you are willing to type in the subroutine’s parameter list).
A better solution is to use the ParamArray keyword to make the subroutine’s final argument a parameter array. A parameter array contains an arbitrary number of parameter values. At run time, the subroutine can loop through the array to process the parameter values.
The DisplayAverage subroutine shown in the following code takes a parameter array named values. It first checks the array’s bounds to make sure it contains at least one value. If the array isn’t empty, the subroutine adds the values it contains and divides by the number of values to calculate the average.
' Display the average of a series of values.
Private Sub DisplayAverage(ParamArray values() As Double)
' Do nothing if there are no parameters.
If values Is Nothing Then Exit Sub
If values.Length = 0 Then Exit Sub
' Display the average.
MessageBox.Show((values.Sum()/values.Length).ToString)
End Sub
The following code shows one way the program could use this subroutine. In this example, DisplayAverage would display the average of the integers 1 through 7, which is 4.
DisplayAverage(1, 2, 3, 4, 5, 6, 7)
Parameter arrays are subject to the following restrictions:
The program can also pass an array of the appropriate data type in place of a series of values. The following two calls to the DisplayAverage subroutine produce the same result inside the DisplayAverage subroutine:
DisplayAverage(1, 2, 3, 4, 5, 6, 7)
Dim values() As Double = {1, 2, 3, 4, 5, 6, 7}
DisplayAverage(values)
An interface defines a set of properties, methods, and events that a class implementing the interface must provide. An interface is a lot like a class with all of its properties, methods, and events declared with the MustOverride keyword. Any class that inherits from the base class must provide implementations of those properties, methods, and events.
The following code defines the IDrawable interface and the IDrawableRectangle that implements it:
Public Interface IDrawable
Sub Draw(gr As Graphics)
Function Bounds() As Rectangle
Property IsVisible As Boolean
End Interface
Public Class DrawableRectangle
Implements IDrawable
Public Function Bounds() As Rectangle Implements IDrawable.Bounds
End Function
Public Sub Draw(gr As Graphics) Implements IDrawable.Draw
End Sub
Public Property IsVisible As Boolean Implements IDrawable.IsVisible
End Class
The IDrawable interface defines a Draw subroutine, a Bounds function, and a property named IsVisible.
The DrawableRectangle class begins with the statement Implements IDrawable. That tells Visual Basic that the class will implement the IDrawable interface. If you make the class declaration, type the Implements statement, and then press the Enter key, Visual Basic automatically fills in the declarations you need to satisfy the interface. In this example, it creates the empty Bounds function, Draw subroutine, and IsVisible property procedures shown here. All you need to do is fill in the details.
If you look at the preceding code, you can see where the subroutine declaration’s Implements interface.subroutine clause comes into play. In this case, the Draw subroutine implements the IDrawable interface’s Draw method.
When you type the Implements statement and press the Enter key, Visual Basic generates empty routines to satisfy the interface; then you don’t need to type the Implements interface.subroutine clause yourself. Visual Basic enters this for you.
The only time you should need to modify this statement is if you change the interface’s name or subroutine name or you want to use some other subroutine to satisfy the interface. For example, you could give the DrawableRectangle class a DrawRectangle method and add Implements IDrawable.Draw to its declaration. Visual Basic doesn’t care what you call the routine, as long as some routine implements IDrawable.Draw.
A subroutine’s statements section contains whatever Visual Basic code is needed to get the routine’s job done. This can include all the usual variable declarations, For loops, If Then statements, and other Visual Basic paraphernalia.
The subroutine’s body cannot include module, class, subroutine, function, structure, enumerated type, or other file-level statements. For example, you cannot define a subroutine within another subroutine.
One statement that I haven’t mentioned before that you can use within a subroutine is Exit Sub. This command makes the subroutine immediately exit and return control to the calling routine. Within a subroutine, the Return statement is equivalent to Exit Sub.
You can use Exit Sub or Return as many times as you like to allow the subroutine to exit under different conditions. For example, the following subroutine checks whether a phone number has a 10-digit or 7-digit format. If the phone number matches a 10-digit format, the subroutine exits. Then if the phone number matches a 7-digit format, the subroutine exits. Finally if the number doesn’t match either format, the subroutine displays an error message to the user.
Private Sub ValidatePhoneNumber(phone_number As String)
' Check for a 10-digit phone number.
If phone_number Like "###-###-####" Then Exit Sub
' Check for a 7-digit phone number.
If phone_number Like "###-####" Then Return
' The phone number is invalid.
MessageBox.Show("Invalid phone number " & phone_number)
End Sub