|< Day Day Up >|
As many of the examples so far have shown, a method is invoked by supplying the name of the method and then a list of values in parentheses. The values in the parentheses are called the arguments to the method call. Each argument in the argument list is matched with the parameter that is in the same position in the parameter list: The first argument matches the first parameter, the second argument matches the second parameter, and so on. The type of each argument must be convertible to the type of the parameter. Once all the arguments have been matched to parameters, any remaining arguments are matched against a parameter array argument if there is one. The type of each argument must be convertible to the element type of the parameter array.
An argument list can omit optional arguments if it likes, as discussed in the previous section. In that case, the default value is supplied in place of the missing argument. Optional arguments can be omitted either implicitly or explicitly. For example:
Sub Main() MsgBox("Hello, world!") ' Omits the last two arguments MsgBox("Hello, world!", , "My App") ' Omits the middle argument End Sub
In the first case, the last two arguments of MsgBox are implicitly omitted. In the second case, the second argument of MsgBox is explicitly left blank and will be filled in by the default value of the parameter.
Arguments and Reference Parameters
As discussed in a previous section, reference parameters are special in that they refer directly to the argument variable that is passed in. But what if the argument being passed in isn't a variable? For example:
Function Multiply(ByVal x As Integer, ByVal y As Integer, _ ByRef OperationOverflowed As Boolean) As Integer Try Multiply = x * y Catch e As OverflowException OperationOverflowed = True End Try End Function Sub Main() Dim Result As Integer Result = Multiply(10, 20, True) Console.WriteLine(Result) End Sub
The constant value True is being passed in to the reference parameter OperationOverflowed , but because True is not a variable, the reference parameter has nowhere to point to. In this kind of case, a reference parameter behaves just like a value parameter. Assigning to the reference parameter does not do anything special.
If an argument variable's type exactly matches the type of a reference parameter, the reference parameter contains a reference directly to the argument variable. This type of reference parameter passing is called true byref . However, if the type of the argument variable and the type of the reference parameter do not exactly match, it is not possible for the reference parameter to hold a direct reference to the argument variable. Instead, the value of the argument variable is copied into a temporary variable that matches the reference parameter type, and the reference parameter gets a reference to the temporary variable. When the method returns, the value in the temporary variable is copied back into the argument variable. This type of reference parameter passing is called copy-in/copy-out .
The differences between true byref and copy-in/copy-out can be quite subtle. For example:
Module Test Dim IntVal As Integer = 10 Dim DoubleVal As Double = 20 Sub PrintValues() Console.WriteLine(IntVal & ":" & DoubleVal) End Sub Sub ModifyValue(ByRef Value As Integer) Value = 30 PrintValues() End Sub Sub Main() ModifyValue(IntVal) ModifyValue(DoubleVal) PrintValues() End Sub End Module
This example will print the following.
30 : 20 30 : 20 30 : 30
The first call to ModifyValue passes an actual reference to the IntVal field because the type of the argument matches the type of the parameter. Thus, when the reference variable Value is modified, the field IntVal is modified. However, the second call to ModifyValue cannot pass DoubleVal as a true byref, because its type does not match the parameter type. Instead, a temporary variable is passed copy-in/copy-out. Thus, when the reference variable Value is modified, only the temporary variable is modified, not the field DoubleVal . After the method returns, however, the value is copied out, so DoubleVal is modified.
When you are calling a method with many arguments, especially optional arguments, it is sometimes convenient to refer to parameters by name instead of position. In this example, the second argument to MsgBox is omitted because it does not matter what its value is.
Sub Main() MsgBox("Hello, world!", , "My App") End Sub
However, instead of supplying a blank argument, the method invocation could have referred to the Title argument by name.
Sub Main() MsgBox("Hello, world!", Title := "My App") End Sub
Arguments that refer to the name of the parameter they match to are called named arguments . Arguments that match to a parameter solely by position are called positional arguments . Named arguments must come after all the positional arguments in the argument list and are matched against the name of the parameter in the method being called.
Normally, only methods that exist on an object at compile time can be invoked. In the following example, a compile error will occur on the call to the method AddOrder because the class Customer does not contain such a method.
Class Customer Dim Name As String End Class Class Order ... End Class Module Test Sub Main() Dim c As Customer = New Customer() ' Error: No such method. c.AddOrder(New Order()) End Sub End Module
Sometimes, however, it is necessary to call a method even though it may not exist (or may not be known to exist) at compile time. This occurs most commonly when you are dealing with COM components that do not expose type information, but it could also occur when you are writing methods that take general Object parameters. When you are making a method call on a value that is typed as Object , all type checking ”including whether the method even exists ”is deferred until runtime. (The only exceptions to this rule are the methods that are explicitly defined on Object , such as GetHashCode .) This style of method invocation is called late binding , because the actual method is not bound until runtime. For example, the following method takes a value typed as Object and calls the method AddOrder on it.
Class Order ... End Class Module Test Sub AddOrder(ByVal o As Object) o.AddOrder(New Order()) End Sub End Module
In this example, the AddOrder method must only exist on the actual type that is passed in to the method at runtime. If no such method exists, or if the method does not take an Order as an argument, a runtime exception will occur.
As discussed in Chapter 8, Modules and Namespaces, conditional compilation can be used to compile code into a program only under certain circumstances. For example, it is common to have two different build configurations: release, which is the final program intended to be released to the public, and debug, which is the program with extra code included to help track down bugs . Here is an example of how this might work.
Function Divide(ByVal x As Integer, ByVal y As Integer) As Integer #If DEBUG Then Debug.Assert(y > 0, "Should never divide by zero.") #End If Return x \ y End Function
In this example, the Divide function asserts that the divisor is not zero. If a caller passes in a divisor that is zero, the Assert function will pop up a dialog that contains the message "Should never divide by zero." The Assert call is put in a conditional compilation block so that the check is not present in the release version, only in the debug version.
Including such debugging code in a program is good programming style, but using conditional compilation to accomplish it can become burdensome in some cases. Besides the fact that adding conditional compilation directives requires more typing, it can also make code hard to read. To assist this, methods can be marked as being conditional methods . A conditional method is called normally, but the call is compiled into the program only if a conditional compilation constant evaluates to True . The method System.Diagnostics.Debug.Assert is such a method. Debug.Assert will be compiled into a program only if the conditional compilation constant DEBUG evaluates to True . Otherwise, the call is ignored. So the preceding example can just be written as follows .
Function Divide(ByVal x As Integer, ByVal y As Integer) As Integer Debug.Assert(y > 0, "Should never divide by zero.") Return x \ y End Function
Conditional methods are defined by adding the System.Diagnostics.ConditionalAttribute attribute to a method. The argument of the attribute is the conditional compilation constant to evaluate. The attribute may be applied multiple times to a method ”if any of the conditional compilation constants evaluate to True , the call will be compiled. For example:
<Conditional("DEBUG"), Conditional("TEST")> _ Sub PrintTestMessage(ByVal Message As String) Console.WriteLine(Message) End Sub
In this example, the conditional method PrintTestMessage will be called only if DEBUG or TEST evaluates to True .
|< Day Day Up >|