Chapter 3: Basic Programming
in Visual Basic .NET
In This Chapter
Declaring and Initializing Variables
Working with Block-Level Scope
Static Variables
Using Static Variables
Using Arrays
Working with New Abstract Data Types
Shadow Variables
Functions and Subroutines
Defining Structures
Using Objects
Exception Handling
Summary
In this chapter, you'll get your first chance to write Visual Basic .NET
code unencumbered with the coding practices and idioms of yesterday. We will use
Visual Basic .NET code and only Visual Basic .NET code. Chapter 3 provides you
with an opportunity to experiment and explore the basic programming concepts
that are the molecules of every program.
This chapter looks at variable declaration and initialization, block scope,
static variables, and more on arrays and abstract data types. I will also
introduce you to the concept of shadow variables, and provide you with some more
examples on defining procedures and structures. Near the end of this chapter, we
will look at the new garbage collector and the impact it has on using objects.
Finally, this chapter wraps up with comprehensive coverage of exception
handling. Because you will use exception handling in all of the code you
writethough perhaps not all proceduresyou need to master the
concepts of exception handling early.
Declaring and Initializing Variables
There is one significant difference in the way you write declarations for
simple types and object types. That difference is the use of the keyword
New. (We'll look at New more in the section "Using
Objects.")
Variables can be declared and initialized in Visual Basic .NET on a single
line. In addition to resulting in fewer lines of code, this new feature can be
applied with some simple rules to make code more robust.
Before we look at several declarative statements, here is an overview of the
best practices regarding variables that we will follow throughout this book:
Always provide an initial value for variables.
Declare and initialize variables in a single statement.
Declare variables immediately before they are used.
Declare variables in the narrowest scope possible.
Use the refactoring "Replace Temp with Query" to keep the
number of temporary variables to an absolute minimum.
In each of the subsections, I will demonstrate several declarators supporting
each of these best practices. In the final subsection, I will demonstrate the
concept of refactoring, and illustrate benefits derived from refactored
code.
Initialize Variables
In programming, so many things can go wrong that we need to hedge as often as
possible to make things go right. These hedges, like splitting aces in
blackjack, are good practices that help us progress deliberately without
sweating details. By providing an initial value for variables, we are providing
a reference point, or known state, that we can use to evaluate a variable.
Essentially, by providing an initial state and controlling the value of
variables, we can always evaluate a variable to determine whether its value is
within an acceptable range. However, if we never expressed what an acceptable
initial value was, we can't test the initial state.
The desirability of a variable having an initial state is exactly why
object-oriented languages have constructors and parameterized constructors (the
section "Using Objects" in this chapter covers these more). It's
so that we can initialize even objects.
The general form of a variable declaration is as follows:
Dim varname As datatype = expression
Note -
Although VB6 code often was encumbered with a pseudo-Hungarian notation prefix
for variables, we will not use the Hungarian notation in this book. The first
reason is that no single notation existed, resulting in many inconsistencies.
A second reason is that we are working with a strongly typed, object-oriented
language, which mitigates the need for prefixes. Consequently, a notation
would only increase our labor.
We will use a very simple notation in this book (you can adopt it or not).
Fields will be prefixed with an F and properties will have an identical name,
without the F, as their underlying field values. This convention was borrowed
from Object Pascal and helps us name fields and properties consistently and
quickly and makes the job of matching fields to properties trivial.
varname is replaced with a suitable variable name. Generally, whole words, no
non- standard abbreviations, and no prefixes are used for variable names (see
the note). datatype is the name of a class, structure, or
ValueType like Double. Expression is a suitable expression for
the data type. (The syntax changes slightly for objects and events. See the
section "Using Objects" for object declarations and Chapter 8 for an
example of WithEvents statements.) Listing 3.1 demonstrates several
declarations, including suitable initial values.
Listing 3.1 Several declaration and initialization statements.
1: Module Module1
2:
3: Sub AHandler(ByVal o As Object, ByVal e As System.EventArgs)
4: Debug.WriteLine("Read chapter 9")
5: End Sub
6:
7:
8: Sub Main()
9:
10: Dim S As String = Command()
11: Debug.WriteLine(S)
12:
13: Dim TodayDate As DateTime = Today()
14: Debug.WriteLine(TodayDate)
15:
16: Dim MyArray() As Double = {3.14159, System.Math.Sqrt(2)}
17: Debug.WriteLine(MyArray(1))
18:
19: Dim MyHandler As EventHandler = AddressOf AHandler
20: MyHandler(Nothing, Nothing)
21:
22: Try
23: Dim MyException As Exception = New Exception("Raise an exception!")
24: Throw MyException
25: Catch e As Exception
26: Debug.WriteLine(e.StackTrace())
27: End Try
28:
29:
30: End Sub
31:
32: End Module
Tip - If you are debugging in the IDE,
you set the command-line arguments by opening the project's properties
pages. Choose Project, Properties, Configuration Properties, Debugging to open
the view. Modify the command-line argument's value to set the command-line
argument.
Listing 3.1 defines some spurious declarators. The examples typify some of
the declarations you are likely to encounter in everyday code. Line 10
demonstrates declaring a string and initializing it to the command-line
arguments. The value of Command will include all of the arguments that
follow the /cmd switch.
Line 13 demonstrates how to initialize a DateTime to today's
date. Line 16 demonstrates initializing an array of doubles; MyArray is
initialized to contain an approximate value for þ and the square root of 2,
returned from System.Math.Sqrt.
Line 19 declares MyHandler as the Delegate type
EventHandler and initializes it to the address of the subroutine
AHandler. Lines 19 and 20 illustrate that Visual Basic .NET can work
with function pointers and introduce the subject of delegates. Delegates are
special classes that support function pointers and are used to implement event
handling in Visual Basic .NET. Chapter 9, "Understanding Delegates,"
gives more information on this subject. (For now, suffice it to say that when
MyHandler is called on line 20, the subroutine on lines 3 through 5 is
actually called.)
Lines 22 to 27 demonstrate declaring and initializing the root exception
object. Line 24 demonstrates throwing an exception, which is similar to raising
an error in VB6. Lines 25 through 27 demonstrate a Catch block. The
last section of this chapter covers exception handling.
Declare and Initialize Variables in a Single
Statement
The biggest problem with uninitialized variables is related to pointers in C
and C++. Because C/C++ pointers can point to any memory location, an
uninitialized C/C++ pointer might be pointing to BIOS memory. Visual Basic .NET
has limited support for pointers, so we are able to get away with the default
initialization of variables to the null equivalent more often.
Tip - By default VB .NET initializes
numeric types to 0, strings to empty strings, and objects to
Nothing.
However, Visual Basic .NET does support delegates and dynamic object
creation. It is preferable that these types have an initial value; otherwise,
your code will be littered with checks for Nothing or an equivalent.
Calling an uninitialized Delegate results in a
NullReferenceException, which is cleaner than crashing because of a
pointer trashing BIOS memory, but still annoying.
As a convenience, it is easier to have one rule for all cases than to make
exceptions to the rule. Thus it is easier to always declare and initialize
variables in a single statement.
Declare Variables Immediately Before First Use
There is no compelling reason to declare variables at the top of a procedure.
Declaring them immediately before first use makes it easy to track the type and
purpose of a local variable.
As an added benefit, if you are able to apply the refactoring "Replace
Temp with Query," having the temporary variable close to its point of use
makes getting rid of the temporary variable easier too. (See the section
"Employ Refactoring: Replace Temp with Query" for more information on
refactoring.)
Declare Variables in the Narrowest Scope
Possible
Global variables are bad. The reason global variables are bad is because the
opportunity for misuse is greatest. The opportunity for misuse of data should be
reduced as much as possible; or, simply put, the narrower the scope, the
better.
You are familiar with module, class, structure, and procedure scope. You may
also use accessibility specifiers to further reduce opportunity for misuse.
Accessibility specifiers like Public, Private,
Protected, Friend, and Protected Friend are discussed
in Chapter 7. Block-level scope has been added to Visual Basic .NET. Block-level
scope is the narrowest scope of all. Refer to the section "Working with
Block-Level Scope" for more information.
Employ Refactoring: Replace Temp with Query
This book does not cover all the details of refactoring. A great book on
refactoring is Martin Fowler's Refactoring: Improving the Design of
Existing Code, published by Addison-Wesley. However, where it is suitable to
mention refactoring, I will do so.
What Is Refactoring?
Refactoring is an outgrowth of XP (Extreme Programming), which has been
popularized by Kent Beck and others. XP includes concepts like pair programming
and resolving problems in the structure of code with refactoring. Refactoring
stems from a dissertation by William Opdike.
My definition of refactoring is "factoring out common code." The
real benefit of refactoring is that it provides a language, or a frame of
reference, for talking about improving code, and offers a set of almost
algebraic rules to make refactoring an orderly process.
We can't summarize Martin Fowler's book here, so for now, just
accept that refactoring is a good thing. Refactoring helps move towards the
fewest occurrences of repetitive code and helps achieve greater opportunity for
reuse. Refactoring may also lead to a means of measuring the qualitative value
of code. Good code is refactored code; bad code often is not.
Replace Temp with Query
"The problem with temps is that they are temporary and local. Because
they can be seen only in the context of the method in which they are used, temps
tend to encourage longer methods" (Fowler, p. 120). Long methods are less
desirable than short methods.
By replacing a temporary variable with a querya function that returns
the value previously maintained by the temporary variablewe shorten the
procedure and increase the likelihood that other fragments of code can use the
query method instead of reconstructing the temporary variable.
Assume that we have two properties: Sale and TaxRate.
TotalSale is defined as the sale plus the cost of taxes. Also, suppose
that we have a fragment of code that declares a local temporary variable to
store TotalSale:
Dim TotalSale As Decimal = Sale * (1 + TaxRate)
Debug.WriteLine(TotalSale)
The Debug.WriteLine statement plays the role of using the temporary
variable.
To replace temporary variables with a query method, first look for temporary
variables that are assigned a value just once, as is TotalSale. (If it
is assigned values more than once in a procedure, you may need to simplify the
procedure or keep the temporary.) Having identified the temporary, perform the
following steps to complete the refactoring:
Define a function, adding a Get to the temporary variable name.
Define the function to return the calculated value of the two fields. (In the
example, Sale and TaxRate represent the two
fields.)
Function GetTotalSale() As Decimal
Return Sale * (1 + TaxRate)End Function
Replace the initialization of the temporary with the function
call.
Dim TotalSale As Decimal = GetTotalSale
Debug.WriteLine(TotalSale)
Compile and test the modification to ensure that the code behaves
identically and yields the same results.
Having confirmed identical behavior, remove the temporary variable and
replace the use of the temporary with the call to the query
method.
Debug.WriteLine(GetTotalSale())
Compile and test.
The purpose of refactoring is to get the same result with simpler code.
Testing after each phase of the refactoring makes it easier to roll back changes
if the code behaves differently.
Critical to Extreme Programming and refactoring is the testing process. The
benefit of the refactoring "Replace Temp with Query" is that long
procedures become shorter, while performing the same operations, and the
frequency of debugging the local temporary calculation decreases to one time,
in the query method.
The benefit of such refactoring often rises over time. The more times you
would have written the calculation for the temporary again, the more savings you
achieve.
Working with Block-Level Scope
Scope relates to opportunity for use. When a name is in scope, you can use
it; when a name is not in scope, you cannot use it.
There are several layers of scope in Visual Basic .NET. From the broadest to
narrowest scope, there is namespace, type (applies equally to class, module, and
structure), procedure, and block scope. Things declared in a broader scope are
accessible to narrower, subordinate scopes directly. For example, a private
class field is accessible to a procedure in the same class and will not cause a
name conflict with a field with the same name in another class.
Additionally, narrower-scoped names are accessible to other areas of code
using the member-of operator (.). For example, the ToString method is
defined as a public method of Object. Because ToString is
public, it is accessible outside the Object class, but you have to
precede the call to ToString with a class reference and the member-of
operator, class.ToString().
Variables declared in a procedure or block have procedure-level or
block-level scope, respectively. A block in a procedure is narrower than the
containing procedure, so procedure variables are accessible in a block, but
block variables are not accessible in the containing procedure. As mentioned in
Chapter 2, block scope is new in Visual Basic .NET.
Block-scoped variables are those variables introduced for the first time in a
For Next, For Each, While End While, or If
Then construct. Any variables introduced with a Dim statement in a
block construct are accessible only in that block when Option Explicit
is On. Consider the following code fragment, using an arbitrary block
to demonstrate block scope behavior:
If ( True ) Then
Dim I As Integer
I = 10
End If
I = 5
If Option Explicit is On, you will get a "name is not
declared" error reported by the compiler at the last line, assuming that
I is not defined in the containing procedure. If Option
Explicit is Off, the compiler will report a "block-level
variable hides variable in enclosing block error" because the implicit
I in the statement I = 5 hides the explicit I in the
If Then block. Further, when Option Explicit is Off,
if I were first used in the block, then I would be accessible
outside the block.
Consider another example:
Option Explicit Off
If (True) Then
I = 10
End If
Debug.WriteLine(I)
This code writes 10 to the Output window.
If you want a variable to be accessible in an outer scope, move the
declaration of the variable to the outer scope, as in this final example:
Option Explicit On
Dim I As Integer
If(True) Then
I = 10
End If
Debug.WriteLine(I)
The variable I is defined outside the block and consequently is
accessible in both the scope where it is defined and the narrower scope of the
If Then block.
A second detail to watch out for is that block variables have to be
initialized between trips in and out of block scope to prevent unexpected
results. When working with block scope, establish a consistent set of rules to
prevent problems related to block scope. The following rules, if applied
consistently, should preclude any problems:
Always set Option Explicit On.
Define variables in the narrowest scope possible.
Apply the "Replace Temp with Query" refactoring discussed
earlier, reducing the number of temporary variables and limiting potential
scope-related problems.
Always initialize variables, especially variables declared in a
block-level scope.
Static Variables
The Static keyword is used to indicate that a variable will maintain
its value between successive calls to a procedure. VB6 supported making all
procedure variables static by adding the Static keyword to the
procedure header. Visual Basic .NET does not support procedure-header use of
Static.
Using Static Variables
The mechanics for declaring static variables require that you replace
Dim with Static as demonstrated in the following function:
Function GetCounter() As Integer
Static Counter As Integer = 0
Counter += 1
Return Counter
End Function
Each time GetCounter is called, Counter will be incremented
by 1. The first time GetCounter is called it will return 1, and each
subsequent call to GetCounter will return Counter + 1. (Note
the use of the new addition and assignment operator, +=.)
Caution - You cannot use the compound
operators, like +=, in an initialization statement for a variable. For
example, Static Counter As Integer += 1 will cause a compiler
error.
A function like GetCounter is a good way to get a unique value
during the program's run.
Static Variables and Memory
Procedure variables are created in stack memory. The stack memory is
temporary memory. Items are added to the stack as local variables are declared
in a procedure and removed when the procedure ends. Static variables are
actually created in the data segment. The memory for static variables is
allocated and remains as long as your program is running, just as variables
declared outside of a procedure in a module are.
Using Arrays
Since about 1990, when C++ began gaining popularity, it has been a
recommended practice to encapsulate arrays of memory in a class. Storing arrays
of data in a class allows the array to perform memory and bounds checking in
conjunction with the class and behind the scenes.
Using an unadorned array requires that you manage bounds checking and
reallocation of memory (using ReDim). On occasion you may want to use
the new array class, but for most new code you will probably find
ArrayList easier to use and less error-prone. ArrayList is
covered in the section "Working with New Abstract Data Types."
Arrays Are Instances of System.Array
All arrays are instances of System.Array. All members of
System.Array were listed in the last chapter in Table 2.10, so we
won't repeat that information here. Keep in mind that even simple arrays
are classes in Visual Basic .NET.
For additional reference, arrays implement the ICloneable,
IList, ICollection, and IEnumerable interfaces.
Declaring Arrays
There are a couple of basic kinds of array declarations. You may declare an
array without a fixed number of elements, represented by the empty parentheses
(), and optionally include an initializer list. Or, you may declare an array
with a fixed number of elements. However, an array with a fixed number of
elements specified cannot also have an initializer list.
Dim StringArray() As String = {"This", "is", "a", "test"}
Dim FixedArray(3) As Double
The size of StringArray is not specified but is indicated by the
initializer list {} containing the words This, is,
a, and test. StringArray will have four elements
indexed from 0 to 3. (Remember that Option Base is not supported, so
the first element of a Visual Basic .NET array is 0.)
Note -
Remember that Visual Basic .NET does not support the range array using the
following syntax:
Dim A(low To high) As datatype
FixedArray contains four elements. In Visual Basic .NET, the number
of elements is always n+1. FixedArray is declared as
FixedArray(3), resulting in four indexable elements from 0 to 3.
Much of the work we used to have to handcraft in VB6 is already implemented
in Visual Basic .NET. The next section demonstrates some of the new capabilities
of arrays.
Using Array Methods
In addition to array initializer lists, there are many methods that you can
take advantage of that make sorting, copying, and finding elements of an array
much easier. Many of these methods are Shared methods (refer to Table
2.10 for details). Listing 3.2 demonstrates several array methods.
Listing 3.2 Demonstration of array methods.
1: Sub Main()
2:
3: Dim StringArray() As String = {"This", "is", "a", "test"}
4: Dim FixedArray(3) As Double
5:
6: Array.Sort(StringArray)
7:
8: Dim E As IEnumerator = StringArray.GetEnumerator()
9:
10: While (E.MoveNext())
11: Debug.WriteLine(E.Current())
12: End While
13:
14: Array.Reverse(StringArray)
15:
16: E.Reset()
17:
18: While (E.MoveNext())
19: Debug.WriteLine(E.Current())
20: End While
21:
22: Debug.WriteLine(Array.IndexOf(StringArray, "test"))
23:
24: Dim NewArray(StringArray.Length) As String
25: Array.Copy(StringArray, NewArray, StringArray.Length)
26:
27: Debug.WriteLine(Array.IndexOf(NewArray, "a"))
28:
29:
30: End Sub
Lines 3 and 4 repeat the declaration of StringArray and
FixedArray from the last section. Line 6 calls the Shared
method Sort, passing StringArray as the array to be sorted.
After line 6 the strings are ordered as a, is, test,
and This. Line 8 declares an IEnumerator interface variable
and the enumerator for StringArray is returned. Using enumerators
allows you to work with a common interface for enumerating many kinds of
data.
The enumerator is positioned before the first element, which makes it
convenient to start iterating with MoveNext (see lines 10 and 18).
MoveNext returns a Boolean indicating whether or not there is a value
at that position. E.Current returns the current element. (Of course you
can still use a For loop and an index to iterate over elements of an
array, if desired.)
Line 14 sorts the array in descending order with the Reverse method.
Again, Reverse is a Shared method, so we pass the array we
want reversed as an argument to the method. Line 16 resets the enumerator
because we left it pointing past the last element on line 12. Line 22
demonstrates finding an element of an array with the IndexOf method.
Line 25 demonstrates copying an array. The number of elements of the source and
target arrays must be identical; on line 24 I used the length of
StringArray to allocate NewArray. You can also use the
ReDim command to resize the target array before copying the array.
Resizing arrays was covered in the section "Resizing Arrays" in
Chapter 2.
Multidimensional Arrays
Visual Basic .NET supports multidimensional arrays and nested arrays. A
multidimensional array is an array containing a comma-delimited list of
array dimensions. A two-dimensional array, for instance, contains two numbers
indicating the size of each dimension. Multidimensional arrays are also referred
to as matrices.
Chapter 2 demonstrated multidimensional arrays. We won't repeat that
information here, but will demonstrate an array of arrays.
We can add any object to the Array type. If we store heterogeneous
objects in an array, we can rely on late binding to determine what an element of
the array is, or we can perform dynamic type checking and conversion. Because
the recommended Option Strict On setting prohibits late binding, I will
demonstrate how to convert the elements of the nested arrays to a specific type
at runtime. (You also need to know how to perform dynamic type conversions to
work with multicast delegates in Chapter 9.)
Note -
Late binding refers to the compiler determining the type of an object
at runtime. For example, in VB6 we might have defined a variable as a variant
and then assigned an object to it with CreateObject. The following
Visual Basic .NET code demonstrates a late bound reference to MS Excel with
Option Strict Off and replacing the Variant required in
VB6 with the Object required in Visual Basic .NET.
Module Module1
Private Excel As Object
Sub Main()
Excel = CreateObject("Excel.Application")
Excel.Visible = True
MsgBox("Stop")
End SubEnd Module
Early binding is indicated when we add a reference to the object
we want to create and declare a variable of that type. An early bound object is
an object whose type is known by the compiler at compile time.
Option Strict forces the compiler to do more work and is a
recommended setting; thus we give up late bound objects for the benefit of
stronger compiler type checking.
To declare an Array object, use the same syntax as any other array
and declare the type as Array. Listing 3.3 demonstrates an example with
an array defined to contain six elements. To insert a nested array into each
indexed position of the array, assign the array object to an indexed position of
the containing array. Nested array elements are accessed by indexing the
containing array or using an enumerator. To access an element of the nested
array, you need to convert the nested array object to a specific type.
Listing 3.3 Nested arrays and dynamic type conversion.
1: Sub Main()
2:
3: Dim StringArray() As String = {"This", "is", "a", "test"}
4: Dim AnArray(5) As Array
5:
6: AnArray(0) = StringArray
7: Debug.WriteLine(CType(AnArray(0), String())(0))
8: End Sub
From the listing, you can easily determine that declaring the outer, generic
array (line 4) is no different than declaring an array of any type. Assigning
StringArray to AnArray(0) nests StringArray into the
first element of AnArray. Line 7 demonstrates using dynamic type
conversion to get the nested array back to a usable form. The code is packed
tightly into a single statement; the next fragment breaks line 7 into a more
verbose listing.
Dim Elem As Object
Elem = AnArray(0)
Dim Temp() As String
Temp = CType(Elem, String())
Dim S As String
S = Temp(0)
Debug.WriteLine(S)
Line 7 from Listing 3.3 translates into the very verbose seven lines of code
in the preceding fragment. AnArray(0) returns an object. CType
converts the Object to a string array. Having the string array, we can
access elements of the string array, and finally write the value of the
string.
Listing 3.3 demonstrates that it is possible to write convoluted, terse code
in Visual Basic .NET. However, the code demonstrates what is possible but not
necessarily prudent. Sometimes the data structure can make it very easy to store
and manage specific kinds of data. If you find yourself coding nested arrays,
hide the complexities of managing the data in a class. As a better alternative,
consider using one of the new abstract data types rather than a
System.Array.
Working with New Abstract Data Types
A revised Collection class similar to the VB6 Collection is
defined in the Microsoft. VisualBasic namespace. Visual Basic .NET has
added several new Abstract Data Types (ADTs) to ultimately replace the
Collection class. The names of the new classes were enumerated in
Chapter 2, including an example of using the Stack class. The
subsections that follow demonstrate ArrayList, HashTable,
SortedList, and Queue.
Members of ArrayList
The ArrayList class is a designed replacement for an unadorned
array. Although arrays in Visual Basic .NET are classes, they are designed to
work similarly to VB6 arrays. This means you have to resize the arrays and
preserve elements manually, which is likely to yield code that is littered with
ReDim statements.
Dynamic array sizing is semantical behavior that belongs to an array. In an
object- oriented programming language, you would expect that such behavior is
defined as part of an array class. Combining storage and capacity management in
a single class is what ArrayList has to offer. You will find
ArrayList easier to use than a System.Array or the VB6-style
collection.
Table 3.1 lists the members of ArrayList.
Table 3.1 ArrayList Members
|
Name
|
Description
|
|
|
Shared Method
|
|
Adapter
|
Wraps an ArrayList around an object that implements
IList
|
|
FixedSize
|
Returns a fixed-size wrapper, allowing elements to be modified but not added
or removed
|
|
ReadOnly
|
Returns a read-only wrapper around an ArrayList
|
|
Repeat
|
Returns an ArrayList containing multiple copies of the argument
value
|
|
Synchronized
|
Returns a thread-safe list wrapper
|
|
|
Instance Property
|
|
Capacity
|
Used to get or change the capacity of an ArrayList
|
|
Count
|
Returns number of elements in the list; capacity may be greater than or equal
to count
|
|
IsFixedSize
|
Returns a Boolean indicating whether or not the ArrayList has a
fixed size
|
|
IsReadOnly
|
Returns a Boolean indicating whether or not the ArrayList is
read-only
|
|
IsSynchronized
|
Returns a Boolean indicating whether or not access to the ArrayList
is synchronized
|
|
Item
|
Used to index elements of the ArrayList
|
|
SyncRoot
|
Returns an object used to synchronize ArrayList access
|
|
|
Instance Method
|
|
Add
|
Appends an element to the ArrayList, increasing the capacity if
necessary
|
|
AddRange
|
Appends elements from an ICollection
|
|
BinarySearch
|
Searches for element using binary algorithm
|
|
Clear
|
Removes all elements
|
|
Clone
|
Performs shallow copy of all elements
|
|
Contains
|
Returns a Boolean indicating whether or not the element is in the
ArrayList
|
|
CopyTo
|
Copies all or part of the ArrayList to a one-dimensional
System.Array
|
|
Equals
|
Determines whether the argument array references the calling array
|
|
GetEnumerator
|
Returns ArrayList enumerator
|
|
GetHashCode
|
Hashing function inherited from Object
|
|
GetRange
|
Copies range of elements to a new ArrayList
|
|
GetType
|
Returns metaclass of ArrayList; inherited from Object
|
|
IndexOf
|
Returns the index of a particular element of the array list
|
|
Insert
|
Inserts an element into the list at specified index
|
|
InsertRange
|
Inserts a range of elements at specified index
|
|
LastIndexOf
|
Returns the index of the last occurrence of object
|
|
Remove
|
Removes the first instance of argument object
|
|
RemoveAt
|
Removes element at specified index
|
|
RemoveRange
|
Removes a range of elements at specified index
|
|
Reverse
|
Sorts the array in reverse order
|
|
SetRange
|
Copies range of elements over the elements in the ArrayList
|
|
Sort
|
Reorders the data in ascending order
|
|
ToArray
|
Copies elements to System.Array
|
|
ToString
|
Returns name of object
|
|
TrimToSize
|
Sets capacity to number of elements
|
The next section demonstrates some of the characteristics of
ArrayList.
Using ArrayList
The biggest benefit of ArrayList over System.Array is that
ArrayList has dynamic capacity management built in. When you use
System.Array, you have to make sure there is enough room for an
element. If not, you have to add capacity to the array with ReDim. On
the other hand, if you use the ArrayList Add,
AddRange, Insert, or InsertRange methods, the
capacity is adjusted as needed.
ArrayList has significant advantages over VB6 arrays but fewer
advantages over Visual Basic .NET System.Array; however, capacity
management is enough of a reason to prefer ArrayList over
System.Array. Many of the methods in ArrayList are similar to
methods in Array (see "Using Array Methods"); therefore, we
will not repeat examples of those methods here. Capacity management and adding
and managing a range of elements are additional features offered in
ArrayList. Let's take a look at examples of using these behaviors.
Listing 3.4 demonstrates behaviors of ArrayList that are not found in
System.Array.
Listing 3.4 Behaviors of ArrayList not found in
System.Array.
1: Sub DemoSetRange()
2:
3: Dim MyArray As New ArrayList()
4:
5: Dim Array1() As Integer = {0, 1, 2, 3, 4, 5}
6:
7: MyArray.InsertRange(0, Array1)
8:
9: Dim I As Integer
10:
11: For I = 0 To MyArray.Count - 1
12: Debug.WriteLine(MyArray(I))
13: Next
14:
15: Debug.WriteLine("Contains 3? " & MyArray.Contains(3))
16:
17: End Sub
The example declares an ArrayList named MyArray using one
of three possible constructors. The constructor on line 3 takes no parameters.
Line 5 allocates a System.Array and initializes the members to the
integers 0 through 5. Line 7 demonstrates ArrayList.InsertRange.
InsertRange takes a start index and an ICollection object.
System.Array implements the ICollection interface, so
System.Array is a suitable argument for InsertRange. In fact,
any class that implements ICollection (HashTable,
Stack, Queue, and SortedList are other examples) is a
suitable argument for InsertRange. Lines 11 through 13 demonstrate that
elements of an ArrayList can be accessed as if it were a simple array.
(Of course you can use the new Enumerator behavior that you saw in
Listing 3.2, as well.)
Line 15 demonstrates the Contains method. Contains takes an
object, which can be a literal integer like 3, and returns a Boolean indicating
whether or not the object is in the ArrayList. In the example,
Option Strict is On so the Boolean returned by
Contains is printed using the ToString method of the Boolean
type.
HashTable
Hash tables use key and value pairs. The key is processed through a hashing
function that is designed to generate a unique value that is then used as an
index into the hash table to the location containing the value. Hash tables
strike a balance between resource usage and speed.
Instead of probing each element for equality to determine whether objects are
equal, simply processing the key provides an index to the location that contains
the associated value. There is a significant amount of research on hash tables,
hashing functions, and key-collision avoidance. (You may have studied some of
them in college if you were a computer science major, but the .NET Framework
provides a HashTable implemented for you.)
The System.Collections.HashTable class implements a hash table
available for you to use. HashTable works externally much like a data
dictionary. Provide a unique key and an associated value, and the
HashTable takes care of the rest.
Suppose you were storing personnel records in memory for quick access. You
might key each record on the Social Security number, and the value would be the
personnel record. (For the demonstration, we will simply store a person's
name to represent the personnel record.)
Listing 3.5 declares a new instance of a hash table and adds some unique
elements to the hash table keyed on pseudo-Social Security numbers. The values
stored in the hash table represent the data associated with the keys. (The key
is the first argument and the value is the second argument of the Add
method.)
Listing 3.5 Storing items in a hash table.
1: Sub DemoHashTable()
2: Dim Hash As New Hashtable()
3: Hash.Add("555-55-5555", "Frank Arndt")
4: Hash.Add("555-55-5556", "Mary Bonnici")
5: Hash.Add("555-55-5557", "Paul Kimmel")
6:
7: Dim Enumerator As IDictionaryEnumerator = Hash.GetEnumerator
8:
9: While (Enumerator.MoveNext())
10: Debug.WriteLine(Enumerator.Key & "=" & _
11: Enumerator.Value)
12: End While
13:
14: End Sub
Tip - Enumerator objects are
read-only. To modify elements of a collection like a HashTable, you can
use a For Next loop, indexing the elements of the collection
directly.
HashTable uses an IDictionaryEnumerator object to iterate
over elements. Line 7 declares an enumerator and lines 9 through 12 iterate over
each element displaying the key and value pairs. (.Key and
.Value were not defined in the IEnumerator interface; they
were added in the IDictionaryEnumerator.)
SortedList
The SortedList ADT is based on the dictionary interface. Recall from
the last section that a dictionary is a collection of key (or name) and value
pairs. SortedList maintains two internal arrays. One keeps track of
keys and the second keeps track of values. As with a hash table, the key values
of a sorted list must be unique.
SortedList has methods similar to ArrayList, in addition to
the key and value pairs introduced with HashTable. SortedList
is defined in the System.Collections namespace. For more information on
SortedList, look in the MSDN help files.
Queue
Queue data structures are also referred to as First In First Out (FIFO) data
structures. Think of a queue as a line. There is a first in line, a last in
line, and everything in between.
Just as Stacks have a language for adding elements to the
collectionPush and Popto denote adding and
removing elements to a Stack, Queue uses the notion of
enqueuing and dequeuing. All of the collection-based ADTs work
with Objects; hence to enqueue means to add an Object to the
queue and to dequeue means to remove an Object from the queue.
Queues are a natural choice when you want the first item put into a
collection to be the first item out. Listing 3.6 demonstrates basic queue
behavior.
Listing 3.6 Basic queue behavior.
1: Sub DemoQueue()
2:
3: Dim Q As New Queue()
4: Q.Enqueue("One")
5: Q.Enqueue("Two")
6:
7: While (Q.Count > 0)
8: Debug.WriteLine(Q.Dequeue)
9: End While
10:
11: End Sub
The output from Listing 3.6 is One and Two. The elements
are dequeued in exactly the same order in which they were enqueued.
Queues implement several of the same COM interfaces as other ADTs defined in
the System.Collections namespace, like ICollection,
IEnumerable, and ICloneable. For this reason queues have many
of the same operations by name as other ADTs.
Assessing the ADTs
For general purpose in-memory storage, ArrayList will suffice. For
key and value pairs, use HashTable or SortedList. If you want
objects stored and retrieved in the same order, use Queue, and if you
want the last element put into a collection to be the first one out, use
Stack. The fundamental behaviors of the collection classes are
identical. The semantic operations are consistent with the type of data
structure.
Shadow Variables
Variables can exist in several places and in broader or narrower scopes.
Sometimes this causes problems because it may be unclear to the compiler what
the code means. For example, when a variable is defined at the procedure level
and a variable with the same name exists in a block within the procedure, the
variable in the block hides the variable with the same name in the outer scope.
In Visual Basic .NET, this is referred to as shadowing. That is, the
narrow scope variable shadows the outer scope variable.
At the block level, the compiler will report an error: "Block-level
variable hides a variable in an enclosing block." An example of causation
follows:
Sub Main()
Dim V As Object
If True Then
Dim V As Object
V = Nothing
End If
End Sub
V in the inner block hides the variable outside of the
If...Then block. To resolve this problem, simply rename or remove the
block-level variable. However, if the problem exists at the module, class, or
structure level, you can use a variety of techniques to clarify your intent.
Suppose Module1 declares a module-level Integer I, and
suppose Sub Main declares a local variable I. Within
Main all references to I will shadow the variable in the
outer, module scope. This scenario is demonstrated by the next code
fragment:
Module Module1
Dim I As Integer
Sub Main()
Dim I As Integer
I = 10
Debug.WriteLine(Module1.I)
End Sub
End Module
All references to I in Main refer to the I defined
in Main. What if you wanted to modify the outer scope's I
variable? In the case of a module, you would simply prefix the reference to
I with the module name. Based on this fragment, if we wanted to refer
to I in the outer scope, Module1.I would clearly indicate our
intent to the compiler.
If the containing entity were a class, we would use the Me
(reference to self) object, as in Me.I. The reason we can use the
module name is that modules are for all intents and purposes classes with all
shared members. The reason we cannot reliably use the class name and have to use
Me is that classes are instantiated many times, and each object will
have a different name.
Visual Basic .NET also introduces the Shadows keyword.
Shadows is used when a subclass reintroduces a name in a superclass,
hiding the name in the superclass and that is what you want to happen. I will
defer the discussion of Shadows until Chapter 7, "Creating
Classes," because it will be beneficial if inheritance and overloading
methods are covered before we continue talking about the Shadows
qualifier.
Functions and Subroutines
If you are familiar with VB6 functions and subroutines, you have the basics
for now. There are some additional changes that you need to familiarize yourself
with, including the revision allowing function results to be assigned with the
Return keyword.
Chapter 5 provides you ample opportunity to experiment with functions,
subroutines, and the new Structure idiom. Chapter 5 also includes some
function qualifiers that you may have forgotten about, including
Optional and ParamArrays.
Defining Structures
The Type idiom has been replaced with the Structure idiom.
Structures are significantly more advanced than types. In addition to the
ability to define aggregate data types, Structures allow you to define
parameterized constructor, property, and method members.
Chapter 5 combines coverage of functions and subroutines with coverage of the
new Structure idiom and examples of defining and using
enumerations.
Using Objects
We have talked a lot about objects. If you have programmed in C++, many of
the idioms related to object-oriented programming will seem familiar to you, but
in this book, we make no assumptions about your experience with object-oriented
programming. Although Chapter 7 discusses defining classes in detail, including
coverage of inheritance, polymorphism, and encapsulation, this section briefly
introduces a few basic concepts, so you will have no trouble working with
objects for now.
Visual Basic .NET supports true object-oriented programming. Critical to this
is the idea of inheritance and constructors. We'll defer our discussion of
inheritance until Chapter 7, but you need to know about constructors now.
What Is a Constructor?
A constructor is a member of a class that has a special roleto
initialize the class. In VB6 this role was satisfied by the
Class_Initialize method, which was called implicitly when you created
an object.
Class_Initialize took no arguments, which meant you were unable to
initialize an object with externally passed-in values. Visual Basic .NET
replaces Class_Initialize with the constructor New().
New is the constructor subroutine that is responsible for initializing
objects. Every object has one constructor that takes no arguments, but
New can be overloaded to take one or more arguments depending on the
needs of the class.
New is invoked, when you use the New keyword when creating
an instance of an object, as in the following:
Dim Button As New Button()
This example creates an instance of a Button componentwhich
replaces the Command control from VB6. Tracing into the Button
class would show that the preceding statement traces right into a subroutine
named New, the constructor in Visual Basic .NET. Every class can have
many constructors and inherits at least one from the Object class.
Listing 3.7 demonstrates a constructor call that dynamically adds a button to a
form at runtime.
Listing 3.7 A Button component is dynamically constructed with
New Button.
1: Private Sub Button1_Click(ByVal sender As System.Object, _
2: ByVal e As System.EventArgs) Handles Button1.Click
3:
4: Static NextTop As Integer = 0
5: Dim Button As New Button()
6: Button.Text = "Button" & Controls().Count
7: Button.Top = NextTop
8: Controls().Add(Button)
9: NextTop = Button.Bottom
10:
11: End Sub
When the Click event is invoked the event handler in Listing 3.7 is
called. Line 4 declares a static variable that tracks the last bottom location
of the most recently created control on line 9. Line 5 constructs a new
Button component. Line 6 provides a unique caption for the button, line
7 positions the button, and line 8 adds the button to the form's
Controls list.
In addition to demonstrating construction, Listing 3.7 demonstrates dynamic
component creation. Refer to Chapter 16, "Designing User Interfaces,"
for more information on dynamic component creation as a means to create flexible
UIs.
Parameterized Constructors
Now that you know a constructor is the term used to refer to the New
method, you might ask what a parameterized constructor is. Quite simply, a
parameterized constructor is a constructor that takes arguments. Because
New is essentially a method, adding arguments to New is the
same as adding arguments to any method.
The slight difference in passing arguments to regular methods versus the
New constructor method is that the arguments are passed in the
parentheses following the data type rather than the New keyword.
Here's an example:
Dim MyException As Exception = New Exception("Raise an exception!")
The statement constructs a new Exception object passing the string
"Raise an exception!" to the constructor. From the code fragment you
might assume that there is a subroutine Exception that takes a
String argument, but in reality the New method is getting the
argument.
Note -
In C++ and Java the constructor has the same name as the class. Object Pascal,
by convention, uses Create for a constructor. These languages pass
the arguments to the constructor in parentheses after the constructor name.
If Visual Basic .NET were to follow this convention, the construction of the
Exception would look like this:
Dim MyException As Exception = New("Raise an exception!") Exception
That
seems a little strange. Just remember that New is the constructor and
param-eters are being passed to New even though it doesn't look as
if this is the case.
Destructors
A destructor is a special method that is used to deinitialize an object. In
VB6 the Class_Terminate method was called implicitly when an object was
destroyed. Visual Basic .NET implements a garbage collector (GC) rather than
explicit object destruction. The GC is a subprogram that runs in the background
returning memory assigned to objects back to the memory pool.
Caution -
You can explicitly run the GC, but it isn't recommended that you do
so. To force the GC to run, type System.GC.Collect.
The GC is designed to run efficiently during times when your program is idle.
Running the GC manually contradicts its intended use.
You cannot tell when the garbage collector will actually run and release your
objects, and no equivalent of Class_Terminate is called in Visual Basic
.NET.
By convention, a method named Dispose can be implemented to perform
any cleanup you may need in your classes, for example, closing an open file.
Chapter 7 will demonstrate how to implement and use Dispose; you may
encounter examples of it in code between now and Chapter 7.
Exception Handling
Exception handling is a new feature in Visual Basic .NET. Exception handlers
are intended to ultimately replace the On Error Goto construct.
Exception handlers provide a more robust means of communicating and managing
errors and have been available in more advanced languages like C++ and Object
Pascal for several years now.
All exception classes are subclassed from the System.Exception
class. The basic exception class contains information similar to that found in
the VB6 Err object. The differences are that there is no global
exception object always availableexception objects are created and raised
when neededand in Visual Basic .NET you can subclass and define your own
exception classes.
Try...Catch
The Try...Catch block is used to catch and handle errors for which
you can provide a resolution. The basic Try...Catch block takes the
following form:
Try
' some protected code
Catch
' resolution on error
End Catch
The protected code goes in the Try part of the block and the
resolution part is written in the Catch block. The Try code is
always executed and the Catch block is executed only in the event of an
error.
VB6 simply shuts down on an unhandled error. In some circumstances it may be
okay to shut down the application, but most of the time, it is another
aggravation users don't need. Visual Basic .NET shuts down the application
and makes an attempt to run a Just-In-Time debugger.
If an error is important enough to shut down your application, exceptions
will at least provide you with an opportunity to do so in an organized manner.
For example, the unavailability of a database may be a sufficient reason to shut
down the application. However, when possible, if you can program a resolution to
the problem and retry the code, exception handlers allow you to do that. If the
problem doesn't need a resolution, you might simply want to show the error
to the user and keep on trucking.
There are two kinds of exception handling. The first is referred to as the
exception handling block, and the second is referred to as the resource
protection block. Both are exception-handling blocks, with the difference being
the keywords used to define the blocks, and their intended uses. The exception
handler designed to handle errors uses the Try...Catch...End Try block
and the resource protection exception handler uses the Try...Finally...End
Try block. These types are demonstrated in the following sub-sections.
Listing 3.8 demonstrates a generic Try...Catch block used to protect
against division by zero.
Note - Recall that division-by-zero for
Doubles yields a return value Infinity. Integer
division-by-zero raises a DivideByZeroException.
Listing 3.8 Generic exception handler that catches all exceptions.
1: Public Sub TestGenericException()
2:
3: Dim I As Integer
4: Dim Numerator As Integer = 5
5: Dim Denominator As Integer = 0
6:
7: Try
8: I = Numerator \ Denominator
9: Debug.WriteLine(I)
10: Catch
11: End Try
12: End Sub
Lines 7 through 11 demonstrate a generic Try...Catch
exception-handling block. This code can be used where you would have written an
On Error Resume Next in VB6. A Catch statement with no code is
referred to as a silent exception. (Basically, a silent exception works
similarly to Resume Next. The reason the demonstration code contains
more than I = 1/0 is because the Visual Basic .NET compiler can catch
direct integer division by zero, whereas the VB6 compiler cannot.)
The Try part of the exception handler is simple enough that if we
want to report the division-by-zero error, we might write a literal message
that indicates what went wrong. To implement notification behavior, add
something like MsgBox("Division by zero error") between the
Catch and End Try lines of code.
Catching Specific Exceptions
Except when you prefer a silent exception, you will probably want to catch
and respond to specific kinds of exceptions. Catching specific exceptions is
supported with a slightly modified syntax.
In Listing 3.8 we know that we are anticipating a potential division-by-zero
error. If we want to catch that error specifically, we need to make a minor
modification to the Catch block to indicate our intentions. Listing 3.9
demonstrates catching a specific type of exception, the
DivideByZeroException.
Listing 3.9 A Catch block for a specific exception.
1: Public Sub TestGenericException()
2:
3: Dim I As Integer
4: Dim Numerator As Integer = 5
5: Dim Denominator As Integer = 0
6:
7: Try
8: I = Numerator \ Denominator
9: Debug.WriteLine(I)
10: Catch e As System.DivideByZeroException
11: MsgBox(e.Message)
12: End Try
13:
14: End Sub
The revision from Listing 3.8 to 3.9 is constrained to lines 10 and 11. The
Catch statement is a declarator. Thus the revision to the
Catch statement as demonstrated on line 10 declares an object of type
System.DivideByZeroException and initializes it with a caught
exception. The Message property of the exception object is displayed in
a message dialog box.
Based on the revised definition of this Catch block, all other
exceptions would be ignored. If you want to catch all exceptions, use
Catch without an exception type. If you want to catch specific
exceptions, list each type of exception you would like to handle with an
additional Catch statement. Listing 3.10 demonstrates catching multiple
exceptions.
Listing 3.10 Multiple Catch clauses.
1: Public Sub BackupFile(ByVal FileName As String)
2: Dim F As System.IO.File
3: Try
4: F.Delete(FileName + ".bak")
5: F.Copy(FileName, FileName + ".bak")
6:
7: Catch e As System.UnauthorizedAccessException
8: MsgBox(e.Message)
9: Catch e As System.IO.FileNotFoundException
10: MsgBox(e.Message)
11: End Try
12: End Sub
Listing 3.10 demonstrates two Catch clauses. Line 7 catches the
UnauthorizedAccessException that might occur when you attempt to delete
or modify a read-only file. Line 9 catches the FileNotFoundException
that might occur when you try to copy a file that doesn't exist.
It is impossible to include all possible ways in which the preceding code
might be written. For example, you could check to see if the files existed
before trying to delete and copy them. As a general rule, if an
If...Then conditional check can preclude the problem, use the
conditional check. However, the exception handler will catch errors you might
not have thought of. In the listing you could check to see if the file exists
before attempting to delete the file, but what if the file exists and is
write-protected? You would still get an
UnauthorizedAccessException.
Think of exception handlers as safety nets for high-wire walkers and an
If...Then conditional similar to deciding whether or not you should be
on the wire in the first place. If you have to be up on the wire, it's a
good idea to have a net. All of the second-guessing in the world won't
catch you if you fall off the wire but have decided to forego the net.
From Bjarne Stroustrup (Stroustrup 1994), the inventor of C++ and father of
object- oriented languages for the PC, we know the following things about the
aim of an exception handler:
"An exception handler isn't intended as simply an alternative
return mechanism ... but specifically as a mechanism for supporting the
construction of fault-tolerant systems."
"An exception handler isn't intended to turn every function
into a fault-tolerant entity, but rather as a mechanism by which a subsystem can
be given a large measure of fault tolerance even if its individual functions are
written without regard for overall error-handling strategies."
And most importantly, "An exception handler isn't meant to
constrain designers to a single 'correct' notion of error-handling,
but to make the language more expressive."
Summarized, this means that the application of exception handling, like many
things in programming, requires a subjective measure of good taste and
experience.
Throughout this book I often include a statement or rationale indicating why
an exception handler was used for each unique occurrence, or rationale for an
exception block in code.
Raising Exceptions
Exceptions are objects. If you elect to raise an exception to indicate an
error condition that a procedure is not going to handle, you create an exception
object like any other object and throw it.
To create an exception instance and throw it, write code as demonstrated in
the following example:
Throw New Exception("message text")
Throw is a Visual Basic .NET keyword used similarly to the
Raise method in VB6. New invokes the constructor for the
exception class, and Exception represents any valid exception class
subclassed from System.Exception.
If you want to rethrow an exception in an exception block, you can add the
Throw statement all by itself in the Catch block.
Throw without an explicit exception object raises the last caught
exception.
Exception Filters
Catch clauses can have a filter applied that enables you to refine
the Catch statement. Consider the example in Listing 3.10. Suppose we
only wanted to catch the exception UnauthorizedAccessException if the
file exists. We could accomplish this goal by adding a when predicate
to the Catch statement:
Catch e As System.UnauthorizedAccessException _
when F.Exists(FileName + ".bak")
Due to the addition of when, this Catch clause will only
catch an UnauthorizedAccessException if the backup file exists (perhaps
the file exists but is read-only).
Try...Finally
Try...Finally blocks are referred to as resource protection
blocks. Although Catch blocks are only invoked upon an exception of
the type indicated in the Catch clausekeeping in mind that
Catch without an exception predicate catches all
exceptionsFinally clauses are always invoked whether there is an
exception or not.
Try...Finally blocks are used to ensure that allocated resources are
cleaned up. The general order of the Try...Finally statement is
illustrated in this algorithmic example:
Allocate resource (for example, open a file)
Try
Use the resource (for example, add some text to the file)
Finally
Clean up the resource
End Try
In this example, only Try, Finally, and End Try
are literals. Demonstrating the Try...Finally block using the
StreamWriter class, we could create a StreamWriter, try to
write some text, and close the writer in the Finally block. Listing
3.11 demonstrates both StreamWriter and the Try...Finally
statement.
Listing 3.11 A resource protection block protecting a file managed by the
StreamWriter object
1: Public Sub WriteText(ByVal FileName As String, _
ByVal SomeText As String)
2:
3: Dim W As New System.IO.StreamWriter(FileName, True)
4: Try
5: W.WriteLine(SomeText)
6: Finally
7: W.Close()
8: End Try
9:
10: End Sub
As described in the algorithm, the StreamWriter is allocated on line
3. Line 5 uses the protected resource to write SomeText, and the
Finally block ensures that the file represented by the
StreamWriter object is closed.
Try...Catch and Try...Finally blocks may be combined to
protect against exceptions and resource corruption. To this end, you may nest or
combine exception-handling blocks as needed for the desired effect. Consider
Listing 3.11 again. What if you want to catch the
UnauthorizedAccessException and ensure that opened streams were
actually closed? We can combine an exception block with a resource protection
block as demonstrated in Listing 3.12.
Listing 3.12 A Try...Finally block nested inside a
Try...Catch block
1: Public Sub WriteText(ByVal FileName As String, _
ByVal SomeText As String)
2:
3: Try
4: Dim W As New System.IO.StreamWriter("sample.txt", True)
5:
6: Try
7: W.WriteLine("Add some text")
8: Finally
9: W.Close()
10: End Try
11:
12: Catch e As System.UnauthorizedAccessException
13: MsgBox(e.Message, MsgBoxStyle.Critical)
14: End Try
15:
16: End Sub
The outer Try...Catch block catches
UnauthorizedAccessException for all of the code in between lines 3 and
12. If we get past line 4, the Try...Finally block will ensure that the
open StreamWriter is closed on line 9.
You will see many examples of Try...Catch and Try...Finally
blocks throughout this book. As with exception-handling blocks, I will elaborate
on the rationale behind Try...Finally blocks where it is useful and not
redundant to do so.
Summary
Chapter 3 provided you with an opportunity to experiment with some of the new
features in Visual Basic .NET. You will use structured exception handling and
create objects in every substantial program you write in Visual Basic .NET.
In addition to demonstrating how to use exception-handling blocks, create
objects, and pass parameters to constructors, this chapter demonstrated how to
declare and initialize variables and use several of the new Abstract Data Types
defined in System. Collections.
Chapter 4 will look at how to get more mileage out of your development
experience by using macros and the Automation Extensibility Model to customize
and extend the Visual Studio IDE.
© Copyright Pearson Education. All rights reserved.
|