Sub and Function Procedures

 

Subs and Functions are blocks of code in your program that are called upon from other parts of your program. Subs and Functions are the building blocks of a program and provide a way to organize and modularize your code.

 

The difference between a Sub and a Function is that a Sub does not produce a return value (i.e., one that can be assigned directly to a variable, whereas a Function does produce a return value).  Both Subs and Functions can be called with or without parameters.

 

Calling a Sub Without Parameters

 

The statement below calls a Sub named "AddEm" (you "call" or invoke a Sub by specifying its name followed by parentheses):

 

AddEm()

 

The Sub "AddEm" would be a section of code in this format:

 

      Private Sub AddEm()

            [statements]

      End Sub

 

When the statement "AddEm()" is executed, VB would execute the statements in the Sub named "AddEm" and then return to the statement the followed "AddEm()".

 

Example (Calling a Sub Without Parameters)

 

When the sample program below is run, the user is prompted to enter two numbers. The Addem Sub is called, which performs the addition. Program flow then returns to the Console.WriteLine statement, which displays the sum of the two numbers.

 

Note that the variables involved are declared at the module-level (i.e., general declarations section, or the section of the module prior to any Subs or Functions) because the variables are common to both the main method and the AddEm Sub procedure.

 

Code:

 

Module Module1

 

 

    'Module-level variables are needed for this example

    Private mintNum1 As Integer

    Private mintNum2 As Integer

    Private mintSum As Integer

 

    Sub Main()

 

        Console.Write("Enter first number: ")

        mintNum1 = Val(Console.ReadLine())

        Console.Write("Enter second number: ")

        mintNum2 = Val(Console.ReadLine())

 

        AddEm()

 

        Console.WriteLine("The sum is: {0}", mintSum)

 

        Console.WriteLine("")

        Console.WriteLine("(Press Enter to close this window.)")

        Console.ReadLine()

 

 

    End Sub

 

    Private Sub AddEm()

 

        mintSum = mintNum1 + mintNum2

 

    End Sub

 

End Module

 

 

Screen-shot:

 

 

Download the project code for the example above here.

 

Calling a Sub With Parameters

 

More often than not, a Sub procedure will be expecting one or more parameters. This enhances the reusability and flexibility of the Sub procedure. The syntax for a Sub procedure is:

 

[Private | Public] Sub SubName[(parameter list)]

 

            [statements]

     

      End Sub

 

where "parameter list" is a comma-separated list of the parameter variables with their data types (i.e., var1 As datatype, var2 As datatype, etc.).

 

For example, if we were to modify the "AddEm" Sub to accept three Integer variables, the header for the "Addem" Sub would look like this:

 

Private Sub AddEm(pintNum1 As Integer, pintNum2 As Integer, pintSum As Integer)

 

The call to AddEm must now specify, or pass, three integer variables to the Sub

 

AddEm(intNum1, intNum2, intSum)

 

The important thing is that the argument list of the calling statement must match the parameter list of the Sub procedure one-for-one in terms of both the number of variables passed and the data types of the variables passed. The names of the corresponding argument/parameter variables can be (and often are) different. With the naming conventions used in these tutorials, the variable names of the parameters in a Sub or Function header start with the letter "p" for "parameter".

 

Example (Calling a Sub With Parameters)

 

In the sample program below, as in the previous example, the user is prompted to enter two numbers. The Addem Sub is called, which performs the addition. Program flow then returns to the Console.WriteLine statement, which displays the sum of the two numbers.

 

The difference this time is that the three variables (the two addends and the sum) are passed as arguments to the Sub Addem. When the call is made, the Sub Addem adds pintNum1 and pintNum2, storing the result in pintSum. When program flow returns to the Console.WriteLine statement, the value of pintSum that was calculated in AddEm is available in its counterpart intSum. (It is important that the keyword ByRef precede pintSum in the header for the Sub Addem procedure. The meaning of ByRef and ByVal is explained a little later below.)

 

The variables involved are declared at the local level (i.e., in the main method, rather than in the general declarations section). A general rule-of-thumb is that variables should be as limited in scope as possible.

 

Code:

 

Module Module1

 

    Sub Main()

 

        'The variables can be declared at the local level

        Dim intNum1 As Integer

        Dim intNum2 As Integer

        Dim intSum As Integer

 

        Console.Write("Enter first number: ")

        intNum1 = Val(Console.ReadLine())

        Console.Write("Enter second number: ")

        intNum2 = Val(Console.ReadLine())

 

        AddEm(intNum1, intNum2, intSum)

 

        Console.WriteLine("The sum is: {0}", intSum)

 

        Console.WriteLine("")

        Console.WriteLine("(Press Enter to close this window.)")

        Console.ReadLine()

 

    End Sub

 

    Private Sub AddEm(ByVal pintNum1 As Integer, _

                      ByVal pintNum2 As Integer, _

                      ByRef pintSum As Integer)

 

        pintSum = pintNum1 + pintNum2

 

    End Sub

 

End Module

 

 

When this example is run, it will behave exactly the same as the previous example.

 

Download the project code for the example above here.

 

ByRef vs. ByVal

 

As indicated in the examples above, variables can be passed to a subroutine "by reference" or "by value", which are specified with the ByRef or ByVal keyword, respectively.  The default in VB.NET is "by value". (This is a change from previous versions of VB, where the default was "by reference".)

 

The differences are as follows:

 

 

 

 

Calling a Function

 

To call a Function, the syntax is identical to an assignment statement that uses a VB built-in function:

 

VariableName = FunctionName[(argument list)]

 

For example, if I made a function called "AddEm" that returned a value (like the sum, for example), I could invoke the function as follows:

 

      intSum = AddEm(intNum1, intNum2)

 

The above statement would cause the following to happen:

 

The (simplified) format of the function procedure itself is:

 

[Private | Public] Function FunctionName[(parameter list)] [As datatype]

 

            [statements]

            FunctionName = value

            [statements]

     

      End Function

 

- or -

 

[Private | Public] Function FunctionName[(parameter list)] [As datatype]

 

            [statements]

            Return value

            [statements]

     

      End Function

 

Note that to return a value from a function, you can either assign the value to the function name or include it in a Return statement. When you assign the return value to the function name, any statements following the assignment statement will be executed before returning to the caller. When you assign the return value using the Return statement, control will immediately return to the caller and any remaining statements in the function procedure will be ignored.

Note:    In previous versions of VB, the return value of a function could only be set via the first method discussed above (i.e., by assigning the return value to the function name). The Return statement was not used to set the return value of a function (the Return statement exists in previous versions of VB, but its purpose is to return control from a block of code executed by the GoSub statement, which is no longer supported in VB.NET).

As when calling a Sub, the items passed in the argument list of the call to the Function must match the parameter list of the Function header in number and datatypes. The datatype of the return value must match the datatype of the target variable name in the assignment statement that calls the Function.

 

Following is the code to implement "AddEm" as a function:

 

Example (Calling a Function)

 

Module Module1

 

    Sub Main()

 

        'The variables can be declared at the local level

        Dim intNum1 As Integer

        Dim intNum2 As Integer

        Dim intSum As Integer

 

        Console.Write("Enter first number: ")

        intNum1 = Val(Console.ReadLine())

        Console.Write("Enter second number: ")

        intNum2 = Val(Console.ReadLine())

 

        intSum = AddEm(intNum1, intNum2)

 

        Console.WriteLine("The sum is: {0}", intSum)

 

        Console.WriteLine("")

        Console.WriteLine("(Press Enter to close this window.)")

        Console.ReadLine()

 

    End Sub

 

    Private Function AddEm(ByVal pintNum1 As Integer, _

                           ByVal pintNum2 As Integer) _

    As Integer

 

        Return (pintNum1 + pintNum2)

 

    End Function

 

End Module

 

 

When this example is run, it will behave exactly the same as the previous two examples.

 

Download the project code for the example above here.

 

Functions That Return Boolean Values

 

Consider the following function, called IsOdd, which determines whether or not a number is odd:

 

    Private Function IsOdd(ByVal pintNumberIn As Integer) As Boolean

 

        If (pintNumberIn Mod 2) = 0 Then

            Return False

        Else

            Return True

        End If

 

    End Function

 

Now consider the following section of code that calls this function:

 

    Sub Main()

 

        Dim intNumIn As Integer

 

        Console.Write("Enter a number: ")

        intNumIn = Val(Console.ReadLine())

 

        If IsOdd(intNumIn) Then

            Console.WriteLine("The number that you entered is odd.")

        Else

            Console.WriteLine("The number that you entered is not odd.")

        End If

 

        Console.WriteLine("")

        Console.WriteLine("(Press Enter to close this window.)")

        Console.ReadLine()

 

    End Sub

 

Screen-shot:

 

 

Download the project code for the example above here.

 

Using Optional Arguments

 

VB allows you to create Subs or Functions that have optional arguments – i.e., arguments that may or may not be passed; and if not passed will assume a default value. Optional arguments are specified in the procedure header with keyword Optional. All Optional arguments must be placed at the end of the argument list (they must follow any required arguments).  Optional arguments that are not passed will take on default values.  The default value of an Optional argument must be set by assigning the optional argument a value in the procedure header. For example, the procedure header

 

      Private Sub MySub(pblnFlag As Boolean, Optional plngNumber As Long = 0)

 

specifies that the last argument is Optional. Any call to MySub must pass it at least one argument (a Boolean) in order to avoid an error.  If the caller so chooses, a second argument (a Long) can also be passed to MySub. Consider the following calls to MySub:

 

      MySub             ' Error: Required argument is missing

      MySub (True)      ' OK – required argument is passed

      MySub (False, 10) ' OK – both required and optional arguments passed

 

In this case, if the argument for plngNumber is not passed, it will have the default value of 0; otherwise, it will have the value of whatever was passed.  Default values can only be used with Optional arguments.

 

Following is an example using a Sub that takes in three Optional arguments with default values.  The Sub calculates the gross pay of an employee:

 

Code:

 

Module Module1

 

    Sub Main()

 

        Console.WriteLine("Hours".Trim.PadLeft(11) & _

                          "Base Rate".Trim.PadLeft(11) & _

                          "O/T Rate".Trim.PadLeft(11) & _

                          "Base Pay".Trim.PadLeft(11) & _

                          "O/T Pay".Trim.PadLeft(11) & _

                          "Gross Pay".Trim.PadLeft(11))

        Console.WriteLine("------".Trim.PadLeft(11) & _

                          "----------".Trim.PadLeft(11) & _

                          "---------".Trim.PadLeft(11) & _

                          "---------".Trim.PadLeft(11) & _

                          "--------".Trim.PadLeft(11) & _

                          "----------".Trim.PadLeft(11))

 

        CalcGrossPay()                  ' Default all three arguments

        CalcGrossPay(25)                ' Default rightmost two arguments

        CalcGrossPay(42, 30)            ' Default rightmost argument

        CalcGrossPay(43, 28, 1.1)       ' Pass all three arguments

        CalcGrossPay(40, , 1)           ' Default second argument

        CalcGrossPay(, 37)              ' Default first and third arguments

        CalcGrossPay(, , 1.25)          ' Default first two arguments

 

        Console.WriteLine("")

        Console.WriteLine("(Press Enter to close this window.)")

        Console.ReadLine()

 

    End Sub

 

    Private Sub CalcGrossPay(Optional ByVal pintHoursWorked As Integer = 40, _

                             Optional ByVal psngBaseHourlyRate As Single = 35, _

                             Optional ByVal psngOvertimeRate As Single = 1.5)

 

        Dim sngBasePay As Single

        Dim sngOvertimePay As Single

        Dim sngGrossPay As Single

 

        If pintHoursWorked <= 40 Then

            sngBasePay = psngBaseHourlyRate * pintHoursWorked

            sngOvertimePay = 0

        Else

            sngBasePay = psngBaseHourlyRate * 40

            sngOvertimePay = (pintHoursWorked - 40) * psngBaseHourlyRate * psngOvertimeRate

        End If

 

        sngGrossPay = sngBasePay + sngOvertimePay

 

        Console.WriteLine(pintHoursWorked.ToString.Trim.PadLeft(11) & _

                          Format(psngBaseHourlyRate, "Fixed").Trim.PadLeft(11) & _

                          Format(psngOvertimeRate, "Fixed").Trim.PadLeft(11) & _

                          Format(sngBasePay, "Currency").Trim.PadLeft(11) & _

                          Format(sngOvertimePay, "Currency").Trim.PadLeft(11) & _

                          Format(sngGrossPay, "Currency").Trim.PadLeft(11))

 

    End Sub

 

End Module

 

Screen-shot of the run:

 

 

Download the project code for the example above here.

 

The next example demonstrates the use of the IsNothing function, which can be used to test if an Optional Object argument has been passed to a procedure. This example produces the same output as the previous example (however, it is MUCH LESS EFFICIENT because it uses the generic "Object" variable type, rather than specific variable types such as Integer and Single – it is presented here for demonstration purposes only):

 

Module Module1

 

    Public Sub Main()

 

        Console.WriteLine("Hours".Trim.PadLeft(11) & _

                          "Base Rate".Trim.PadLeft(11) & _

                          "O/T Rate".Trim.PadLeft(11) & _

                          "Base Pay".Trim.PadLeft(11) & _

                          "O/T Pay".Trim.PadLeft(11) & _

                          "Gross Pay".Trim.PadLeft(11))

        Console.WriteLine("------".Trim.PadLeft(11) & _

                          "----------".Trim.PadLeft(11) & _

                          "---------".Trim.PadLeft(11) & _

                          "---------".Trim.PadLeft(11) & _

                          "--------".Trim.PadLeft(11) & _

                          "----------".Trim.PadLeft(11))

 

        CalcGrossPay()              ' Default all three arguments

        CalcGrossPay(25)            ' Default rightmost two arguments

        CalcGrossPay(42, 30)        ' Default rightmost argument

        CalcGrossPay(43, 28, 1.1)   ' Pass all three arguments

        CalcGrossPay(40, , 1)       ' Default second argument

        CalcGrossPay(, 37)          ' Default first and third arguments

        CalcGrossPay(, , 1.25)      ' Default first two arguments

 

        Console.WriteLine("")

        Console.WriteLine("(Press Enter to close this window.)")

        Console.ReadLine()

 

    End Sub

 

    Private Sub CalcGrossPay(Optional ByVal pobjHoursWorked As Object = Nothing, _

                             Optional ByVal pobjBaseHourlyRate As Object = Nothing, _

                             Optional ByVal pobjOvertimeRate As Object = Nothing)

 

        Dim sngBasePay As Single

        Dim sngOvertimePay As Single

        Dim sngGrossPay As Single

 

        If IsNothing(pobjHoursWorked) Then pobjHoursWorked = 40

        If IsNothing(pobjBaseHourlyRate) Then pobjBaseHourlyRate = 35

        If IsNothing(pobjOvertimeRate) Then pobjOvertimeRate = 1.5

 

        If pobjHoursWorked <= 40 Then

            sngBasePay = pobjBaseHourlyRate * pobjHoursWorked

            sngOvertimePay = 0

        Else

            sngBasePay = pobjBaseHourlyRate * 40

            sngOvertimePay = (pobjHoursWorked - 40) * pobjBaseHourlyRate * pobjOvertimeRate

        End If

 

        sngGrossPay = sngBasePay + sngOvertimePay

 

        Console.WriteLine(pobjHoursWorked.ToString.Trim.PadLeft(11) & _

                          Format(pobjBaseHourlyRate, "Fixed").Trim.PadLeft(11) & _

                          Format(pobjOvertimeRate, "Fixed").Trim.PadLeft(11) & _

                          Format(sngBasePay, "Currency").Trim.PadLeft(11) & _

                          Format(sngOvertimePay, "Currency").Trim.PadLeft(11) & _

                          Format(sngGrossPay, "Currency").Trim.PadLeft(11))

    End Sub

 

End Module

 

Note that data of any type can be assigned to an Object variable; in the procedure above, the default numeric values are assigned to the optional Object variables. In previous versions of VB, the Variant data type was used to hold any type of value; in VB.NET, the Variant data type is not supported, but the Object data type assumes this functionality. In addition, previous versions of VB used the IsMissing function to determine whether or not an optional Variant argument was passed to a procedure; in VB.NET, the IsMissing function is not supported, but the IsNothing function assumes the functionality for testing whether or not an optional Object argument has been passed to a procedure.

 

Download the project code for the example above here.

 

Using Named Arguments

 

The following example demonstrates the use of the named arguments, where you can specify the arguments to be passed in the calling statement using the syntax

 

            ArgumentName:=value

 

Using named arguments, the arguments to the procedure can be passed in any order.  All required arguments must be passed, but optional arguments can be omitted if desired. Using named arguments saves coding when calling a procedure that has a lot of optional arguments but you only need to pass a few of them.

 

Following is the code for GrossPay program using named arguments:

 

Module Module1

 

    Sub Main()

 

        Console.WriteLine("Hours".Trim.PadLeft(11) & _

                          "Base Rate".Trim.PadLeft(11) & _

                          "O/T Rate".Trim.PadLeft(11) & _

                          "Base Pay".Trim.PadLeft(11) & _

                          "O/T Pay".Trim.PadLeft(11) & _

                          "Gross Pay".Trim.PadLeft(11))

        Console.WriteLine("------".Trim.PadLeft(11) & _

                          "----------".Trim.PadLeft(11) & _

                          "---------".Trim.PadLeft(11) & _

                          "---------".Trim.PadLeft(11) & _

                          "--------".Trim.PadLeft(11) & _

                          "----------".Trim.PadLeft(11))

 

        CalcGrossPay()

        CalcGrossPay(pintHoursWorked:=25)

        CalcGrossPay(pintHoursWorked:=42, psngBaseHourlyRate:=30)

        CalcGrossPay(pintHoursWorked:=43, psngBaseHourlyRate:=28, psngOvertimeRate:=1.1)

        CalcGrossPay(pintHoursWorked:=40, psngOvertimeRate:=1)

        CalcGrossPay(psngBaseHourlyRate:=37)

        CalcGrossPay(psngOvertimeRate:=1.25)

 

        Console.WriteLine("")

        Console.WriteLine("(Press Enter to close this window.)")

        Console.ReadLine()

 

    End Sub

 

    Private Sub CalcGrossPay(Optional ByVal pintHoursWorked As Integer = 40, _

                             Optional ByVal psngBaseHourlyRate As Single = 35, _

                             Optional ByVal psngOvertimeRate As Single = 1.5)

 

        Dim sngBasePay As Single

        Dim sngOvertimePay As Single

        Dim sngGrossPay As Single

 

        If pintHoursWorked <= 40 Then

            sngBasePay = psngBaseHourlyRate * pintHoursWorked

            sngOvertimePay = 0

        Else

            sngBasePay = psngBaseHourlyRate * 40

            sngOvertimePay = (pintHoursWorked - 40) * psngBaseHourlyRate * psngOvertimeRate

        End If

 

        sngGrossPay = sngBasePay + sngOvertimePay

 

        Console.WriteLine(pintHoursWorked.ToString.Trim.PadLeft(11) & _

                          Format(psngBaseHourlyRate, "Fixed").Trim.PadLeft(11) & _

                          Format(psngOvertimeRate, "Fixed").Trim.PadLeft(11) & _

                          Format(sngBasePay, "Currency").Trim.PadLeft(11) & _

                          Format(sngOvertimePay, "Currency").Trim.PadLeft(11) & _

                          Format(sngGrossPay, "Currency").Trim.PadLeft(11))

    End Sub

 

End Module

 

Download the project code for the example above here.

 

 

Exiting a Sub or Function Early

 

If you need to exit a Sub or Function procedure "early", you can use the Exit Sub or Exit Function statements, respectively.

 

Example:

 

Dim intNumIn As Integer

 

Console.WriteLine("Enter a non-zero number: ")

intNumIn = Val(Console.ReadLine())

If intNumIn = 0 Then

Console.WriteLine ("You entered zero or a non-numeric value.")

Exit Sub

End If  

' processing will continue here only if a non-zero number was entered

Console.WriteLine "The number you entered was: " & intNumIn

' do some other stuff