Chapter 6: Subroutines and Functions


Subroutines and functions enable you to break an otherwise unwieldy chunk of code into manageable pieces. They enable you to extract code that you may need to use under more than one circumstance and place it in one location where you can call it as needed. This not only reduces repetition within your code; it also enables you to maintain and update the code in a single location.

A subroutine performs a task for the code that invokes it. A function performs a task and then returns some value. The value may be the result of a calculation, or a status code indicating whether the function succeeded or failed.

Together, subroutines and functions are sometimes called routines or procedures. They are also sometimes called methods, particularly when they are subroutines or functions belonging to a class. Subroutines are also occasionally called sub procedures.

This chapter describes subroutines and functions. It explains the syntax for declaring and using each in a Visual Basic application. It also provides some tips for making routines more maintainable.

Subroutines

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.

attribute_list

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. The following 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.

  #Const DEBUG_LIST_CUSTOMERS = True ' #Const DEBUG_LIST_EMPLOYEES = True Private Sub Form1_Load(ByVal sender As System.Object, _  ByVal e As System.EventArgs) Handles MyBase.Load     ListCustomers()     ListEmployees() End Sub <Conditional("DEBUG_LIST_CUSTOMERS")> _ Private Sub ListCustomers()     Debug.WriteLine("ListCustomers") End Sub <Conditional("DEBUG_LIST_EMPLOYEES")> _ Private Sub ListEmployees()     Debug.WriteLine("ListEmployees") End Sub 

The following text shows the output from this program:

  ListCustomers 

Visual Basic 2005 defines around 200 attributes. Many have fairly specialized purposes that won’t interest you most of the time, but some are pretty useful. 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 only useful in very specialized circumstances, so it isn’t described here.

Many attributes give metadata for editors and the IDE, so you will often see their effects only when you view an object in an editor or the IDE. If you are building a control or component, you can put one on a form and then see its properties in the Properties window. In that case, many kinds of attributes will be useful. If you’re building an Employee class that’s used only in code, fewer attributes are useful in any obvious way.

However, Visual Basic 2005 comes with a powerful PropertyGrid control that lets you display an object’s properties on a form much as the Properties window displays them to a developer. That control honors all of the property-related attributes and gives them a whole new level of usefulness.

The following list describes some of the most useful attributes. Most of them are in the System.Component Model 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.

  • AttributeUsage - You can build your own custom attributes by inheriting from the Attribute class. You can give your attribute class the AttributeUsage attribute to specify how your attribute can be used. You can determine whether an item can have multiple instances of your attribute, whether your attribute can be inherited by a derived class, and the kinds of things that can have your attribute (assembly, class, method, and so forth).

  • Browsable - This indicates whether a property or event should be displayed in an editor such as the Properties window. If you pass the attribute’s constructor the value False, the Properties window does not display the property.

  • Category - This indicates the grouping that should hold the property or event in a visual designer such as the Properties window. For example, if the user clicks the Categorized button in the Properties window, the window groups the properties by category. This attribute tells which category should hold the property. Note that the category names are not magic. You can use any string you like and the Properties window will make a new category for you if necessary.

  • DefaultEvent - This gives a class’s default event name. If the class is a control or component and you double-click on it in a form, the code editor opens to this event. For example, the default event for a Button is Click, so when you double-click a Button at design time, the code editor opens the control’s Click event handler.

  • DefaultProperty - This gives a class’s default property name. Suppose that the Employee component has LastName set as its default property. Then suppose that you select the form and click on the FormBorderStyle property in the Properties window. Now you click on an Employee. Because Employee doesn’t have a FormBorderStyle property, the Properties window displays its default property: LastName.

  • DefaultValue - This gives a property a default value. If you right-click on the property in the Properties window and select Reset, the property is reset to this value. Be sure to use a valid value. For example, don’t set this to the string “unknown” if the property is an Integer.

  • Description - This gives a description of the item. If a property has a Description and you select the property in the Properties window, the window displays the description text at the bottom.

    Visual Basic 2005 carries this one step further and also allows you to use XML comments to provide a description of routines and their parameters for use by IntelliSense. For more information, see the section “XML Comments” in Chapter 3.

  • Localizable - This determines whether a property should be localizable. If this is True, localized values are automatically stored in the appropriate resource files. If this is False (the default), all locales share the same property value.

    To try this out, set the form’s Localizable property to True and enter a value for the property. Then set the form’s Language property to another language and give the localizable property a new value. Visual Basic automatically applies the right value for the user’s locale when it runs the program.

  • MergableProperty - This indicates whether or not the property can be merged with the same property provided by other components in the Properties window. If this is False and you select more than one instance of a control with the property, the Properties window does not display the property.

    If this is True and you select more than one control with the property, the Properties window displays the value if the controls all have the same value. If you enter a new value, all of the controls are updated. This is the way the Text property works for TextBox, Label, and many other kinds of controls.

  • ParenthesizePropertyName - This indicates whether editors such as the Properties window should display parentheses around the property’s name. If the name has parentheses, the Properties window moves it to the top of the list when displaying properties alphabetically or to the top of its category when displaying properties by category.

  • ReadOnly - This indicates whether designers should treat this property as read-only. For example, the Properties window displays the property grayed out and doesn’t let the user change its value. This attribute is a little strange in practice because ReadOnly is a Visual Basic keyword. If you enter just the attribute name ReadOnly, Visual Basic gets confused. Either use the full name System.ComponentModel.ReadOnly or enclose the name in square brackets as in <[ReadOnly](True)> .

  • RecommendedAsConfigurable - This indicates that a property should be tied to the configuration file. When you select the object at design time and expand the “(Dynamic Properties)” item, the property is listed. If you click the ellipsis to the right, a dialog appears that lets you map the property to a key in the configuration file.

    RefreshProperties - This indicates how an editor should refresh the object’s other properties if this property is changed. The value can be Default (do not refresh the other properties), Repaint (refresh all other properties), or All (requery and refresh all properties).

    Conditional - This indicates that the method is callable if a compile-time constant such as DEBUG or MY_CONSTANT is defined. If the constant is not defined, code for the method is still generated and parameters in the method call are checked against the parameter types used by the method, but calls to the method are ignored at runtime. If the method has more than one Conditional attribute, the method is callable if any of the specified compile-time constants is defined.

    Note that the constant must be defined in the main program not in the component if you are building a component. Select the main program, open the Project menu, select the Properties item at the bottom, open the Configuration Properties folder, click Build, and in the Custom constants text box enter a value such as IS_DEFINED=True.

    You can also use the compiler directive #if to exclude code completely from compilation. However, if you eliminate a method in this way, any calls to the routine will generate compile-time errors because the method doesn’t exist. The Conditional attribute lets you hide a method while still allowing the code to contain calls to it.

  • DebuggerHidden - This tells debuggers whether a method should be debuggable. If Debugger?Hidden is True, the IDE skips over the method and will not stop at breakpoints inside it.

  • DebuggerStepThrough - This tells debuggers whether to let the developer step through a method in the debugger. If DebuggerStepThrough is True, the IDE will not step through the method, although it will stop at any breakpoints inside it.

  • ToolboxBitmap - This tells the IDE where to find a control or component’s Toolbox bitmap. This can be a file, or it can be a type in an assembly that contains the bitmap and the bitmap’s name in the assembly. It’s awkward but essential if you’re developing controls or components.

  • NonSerializedAttribute - This indicates that a member of a serializable class should not be serialized. This is useful for excluding values that need not be serialized.

  • Obsolete - This indicates that the item (class, method, property, or whatever) is obsolete. Optionally, you can specify the message that the code editor should display to the developer if code uses the item (for example, “Use the NewMethod instead”). You can also indicate whether the IDE should treat using this item as a warning or an error.

  • Serializable - This indicates that a class is serializable. All public and private fields are serialized by default. Note that some routines require a class to be serializable even though you don’t use the serialization yourself. Also note that attributes in the System.Xml.Serialization namespace can provide a lot of control over serializations.

  • ThreadStaticAttribute - This indicates that a Shared class variable should not be shared across threads. Different threads get their own copies of the variable and all instances of the class within each thread share the thread’s copy.

Tip 

Finding the attributes that you need for a particular task can be tricky. It helps to realize that attribute classes inherit either directly or indirectly from the Attribute class. You can get information about the Attribute class at msdn2.microsoft.com/en-us/library/system.attribute.aspx. You can see a list of classes that inherit from Attribute at msdn2.microsoft.com/en-us/ library/2e39z096.aspx.

inheritance_mode

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:

  • Overloads - Indicates that the subroutine has the same name as another subroutine defined for this class. The parameter list must be different in the different versions so that Visual Basic can tell them apart (if they are the same, this works just like Overrides described next). If you are overloading a subroutine defined in a parent class, you must use this keyword. If you are overloading only subroutines in the same class, you can omit the keyword. If you use the keyword in any of the overloaded subroutines, however, you must include it for them all.

  • Overrides - Indicates that this subroutine replaces a subroutine in the parent class that has the same name and parameters.

  • Overridable - Indicates that a derived class can override this subroutine. This is the default for a subroutine that overrides another one.

  • NotOverridable - Indicates that a derived class cannot override this subroutine. You can only use this with a subroutine that overrides another one.

  • MustOverride - Indicates that any derived classes must override this subroutine. When you use this keyword, you omit all subroutine code and the End Sub statement, as in the following code:

      MustOverride Sub Draw() MustOverride Sub MoveMap(ByVal X As Integer, ByVal Y As Integer) MustOverride Sub Delete() ... 

    If a class contains a subroutine declared MustOverride, you must declare the class using the MustInherit keyword. Otherwise, Visual Basic won’t know what to do if you call this subroutine, because it contains no code.

    MustOverride is handy for defining a subroutine that derived classes must implement, but for which a default implementation in the parent class doesn’t make sense. For example, suppose that you make a Drawable class that represents a shape that can be drawn and that you will derive specific shape classes such as Rectangle, Ellipse, Line, and so forth. To let the program draw a generic shape, the Drawable class defines the Draw subroutine. Because Drawable doesn’t have a particular shape, it cannot provide a default implementation of that subroutine. To require the derived classes to implement Draw, the Drawable class declares it MustOverride.

  • Shadows - Indicates that this subroutine replaces an item (probably a subroutine) in the parent class that has the same name, but not necessarily the same parameters. If the parent class contains more than one overloaded version of the subroutine, this subroutine shadows them all. If the derived class defines more than one overloaded version of the subroutine, they must all be declared with the Shadows keyword.

  • Shared - Indicates that this subroutine is associated with the class itself, rather than with a specific instance of the class. You can invoke it using the class’s name (ClassName.SharedSub) or using a specific instance (class_instance.SharedSub). Because the subroutine is not associated with a specific class instance, it cannot use any properties or methods that are provided by a specific instance. The subroutine can only use other Shared properties and methods, as well as globally available variables.

accessibility

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 fol-lowing list explains these keywords.

  • Public - Indicates that there are no restrictions on the subroutine. Code inside or outside of the subroutine’s class or module can call it.

  • Protected - Indicates that the subroutine is accessible only to code in the same class or in a derived class. You can only use the Protected keyword with subroutines declared inside a class.

  • Friend - Indicates that the subroutine is available to all code inside or outside of the subroutine’s module within the same project. The difference between this and Public is that Public allows code outside of the project to access the subroutine. This is generally only an issue for code libraries (DLLs) and control libraries. For example, suppose that you build a code library containing dozens of routines and then you write a program that uses the library. If the library declares a subroutine with the Public keyword, the code in the library and the code in the main program can use the subroutine. In contrast, if the library declares a subroutine with the Friend keyword, only the code in the library can access the subroutine, not the code in the main program.

  • Protected Friend - Indicates that the subroutine has both Protected and Friend status. The subroutine is available only within the same project and within the same class or a derived class.

  • Private - Indicates that the subroutine is available only within the class or module that contains it.

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.

subroutine_name

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.

parameters

The parameters section of the subroutine declaration defines the arguments that the subroutine takes as parameters. The parameter declarations define the numbers and types of the parameters. This section also gives the names by which the subroutine will know the values.

Declaring parameters is very similar to declaring variables. See Chapter 4 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.

ByVal

If you include the optional ByVal keyword before a parameter’s declaration, the subroutine makes its own local copy of the parameter with procedure scope. The subroutine can modify this value all it wants and the corresponding value in the calling procedure isn’t changed.

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 declares its parameter X with the ByVal keyword. It doubles X and displays the new value. Because the parameter X is declared ByVal, the subroutine has its own local copy of the variable, so doubling it doesn’t change the value of the variable A in the main program. When the subroutine ends and the main program resumes, it displays the 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(ByVal X As Integer)     X *= 2     Debug.WriteLine("DisplayDouble: " & X) End Sub 

The following text shows the results:

  Main: 12 DisplayDouble: 24 Main: 12 

ByRef

If you declare a parameter with the ByRef keyword, the subroutine does not create a separate copy of the parameter variable. Instead, it uses a reference to the original parameter passed into the subroutine and any changes the subroutine makes to the value are reflected in the calling subroutine.

Consider the following code. This code is the same as the previous example except that the DisplayDouble subroutine declares its parameter using the ByRef keyword. As before, 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 

Arrays Declared ByVal and ByRef

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.

The DoubleArrayValues subroutine shown in the following code has a parameter named arr. This parameter is an array of integers and is declared ByVal. The routine loops through the array, doubling each of its values. It then loops through the array, displaying the new values. Next, the subroutine assigns the variable arr to a new array of integers. It loops through the array, again displaying the new values.

  Private Sub DoubleArrayValues(ByVal arr() As Integer)     ' Double the values.     For i As Integer = arr.GetLowerBound(0) To arr.GetUpperBound(0)         arr(i) *= 2     Next i     ' Display the values.     For i As Integer = arr.GetLowerBound(0) To arr.GetUpperBound(0)         Debug.WriteLine(arr(i))     Next i     Debug.WriteLine("----------")     ' Create a new array of values.     arr = New Integer() {-1, -2}     ' Display the values.     For i As Integer = arr.GetLowerBound(0) To arr.GetUpperBound(0)         Debug.WriteLine(arr(i))     Next i     Debug.WriteLine("----------") End Sub 

The following code declares an array of integers containing the values 1, 2, and 3. It invokes the subroutine DoubleArrayValues and then loops through the array, displaying the values after DoubleArrayValues returns.

  Dim the_values() As Integer = {1, 2, 3} DoubleArrayValues(the_values) For i As Integer = the_values.GetLowerBound(0) To the_values.GetUpperBound(0)     Debug.WriteLine(the_values(i)) Next i 

The following text shows the results. The DoubleArrayValues subroutine lists the array’s doubled values 2, 4, 6, assigns a new array to its local variable arr, and then displays the new values -1 and -2. When DoubleArrayValues returns, the main program displays its version of the values. Notice that the values were updated by DoubleArrayValues but that the subroutine’s assignment of its arr variable to a new array had no effect on the main program’s array the_values.

  2 4 6 ---------- -1 -2 ---------- 2 4 6 

Now suppose that the subroutine DoubleArrayValues was declared with the following statement:

  Private Sub DoubleArrayValues(ByRef arr() As Integer) 

In this case, when DoubleArrayValues assigns a new array to its arr variable, the calling routine sees the change, so the the_values array receives the new array. The following text shows the new results:

  2 4 6 ---------- -1 -2 ---------- -1 -2 

Parenthesized Parameters

There are a couple ways that a subroutine can fail to update a parameter declared using the ByRef keyword. 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 4 has more to say about parameters declared with the ByVal and ByRef keywords.

Optional

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 takes an optional string parameter. If the calling routine provides this parameter, the subroutine displays it. If the calling routine leaves this parameter out, then DisplayError displays 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 it omits the parameter so DisplayError presents its default message.

  Private Sub DisplayError(Optional ByVal error_message As String = _  "An error occurred")     MsgBox(error_message) End Sub Private Sub PlaceOrder(ByVal the_customer As Customer, _  ByVal 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 

Optional parameters must go at the end of the parameter list. If one parameter uses the Optional keyword, all of the following parameters must use it, too.

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 ByVal X As Integer = 0, _      Optional ByVal Y As Integer = 0, _      Optional ByVal Width As Integer = 100, _      Optional ByVal 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.

Optional versus Overloading

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(ByVal employee_name As String)     FireEmployee(employee_name, "Unknown reason") End Sub Public Sub FireEmployee(ByVal employee_name As String, ByVal 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.

Parameter Arrays

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 runtime, 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 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(ByVal ParamArray values() As Double)     ' Do nothing if there are no parameters.     If values Is Nothing Then Exit Sub     If values.Length < 1 Then Exit Sub     ' Calculate the average.     Dim total As Double = 0     For i As Integer = LBound(values) To UBound(values)         total += values(i)     Next i     ' Display the result.     MessageBox.Show((total / 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:

  • A subroutine can have only one parameter array, and it must come last in the parameter list.

  • All other parameters in the parameter list must not be optional.

  • All parameter lists are declared ByVal, so any changes the subroutine makes to the array’s contents do not affect the calling routine.

  • Parameter array values are implicitly optional, so the calling routine can provide any number of values (including zero) for the array. However, you cannot use the Optional keyword when you declare the parameter array.

  • All the items in the parameter array must have the same data type. However, you can use an array that contains the generic Object data type and then it can hold just about anything. The downside is you may need to convert the items into a more specific type (for example, using DirectCast or CInt) to use their features.

The calling routine can pass any number of values (including zero) for the parameter array. It can also pass the value Nothing, in which case the subroutine’s parameter array has value Nothing.

The program can also pass an array of the appropriate data type in place of the parameter array 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) 

Implements interface.subroutine

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 IDrawable interface shown in the following code 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.

Tip 

Developers often begin the name of interfaces with a capital I so that it’s obvious that it’s an interface.

  Public Interface IDrawable     Sub Draw(ByVal gr As Graphics)     Function Bounds() As Rectangle     Property IsVisible() As Boolean End Interface Public Class DrawableRectangle     Implements IDrawable     Public Function Bounds() As System.Drawing.Rectangle _       Implements IDrawable.Bounds     End Function     Public Sub Draw(ByVal gr As System.Drawing.Graphics) _       Implements IDrawable.Draw     End Sub     Public Property IsVisible() As Boolean Implements IDrawable.IsVisible         Get         End Get         Set(ByVal Value As Boolean)         End Set     End Property End Class  

If you look at the previous 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.

statements

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, Try blocks, 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 new statement 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. If the number doesn’t match either format, the subroutine displays an error message to the user.

  Private Sub ValidatePhoneNumber(ByVal 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.     MsgBox("Invalid phone number " & phone_number, _         MsgBoxStyle.Exclamation, _         "Invalid Phone Number") End Sub  




Visual Basic 2005 with  .NET 3.0 Programmer's Reference
Visual Basic 2005 with .NET 3.0 Programmer's Reference
ISBN: 470137053
EAN: N/A
Year: 2007
Pages: 417

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net