It was a dark and stormy night. Hector gazed wearily through his bloodshot eyes, through the black-rimmed corrective lenses, and through the haze of the fluorescent overhead lights at the phosphor-enriched display. Had it really been four months since he started the six-month project? Did his boss really threaten to fire him after seeing his progress? It seemed like all of those MS-DOS programs he had written for the company over the years meant nothing. Why did he promise to port the company’s main internal system to Windows? In a moment of despair, tears streamed down his cheeks, diluting his last remaining can of Jolt Cola.
It’s 8:00 a.m. A loud thump on Hector’s desk brings him suddenly out of his slumber, the drool still trickling from the corner of his mouth. What’s that? What’s that box on his desk? “V-i-s-u-a-l B-a-s-i-c?” A note on the box says to rewrite his code in “this.” Desperate to try anything, Hector installs the three floppy disks on his ’386 powerhouse.
Six weeks later, Hector has completed the project, ahead of schedule, feature-complete, and with the accolades of his boss and department. And it’s all due to Visual Basic. But VB didn’t just improve his programming life. Overall, he’s happier, has kicked the caffeine habit, is able to bench-press 300 pounds, no longer walks with a limp, has increased libido, and has whiter teeth. “Thank you, Visual Basic 1.0!”
It’s possible that I got a few of the details wrong in Hector’s life. But for many business developers, Visual Basic 1.0 was a breath of fresh air. It’s not that they could do more with Visual Basic; programs written in C were more powerful and had greater flexibility. But business programmers didn’t always need that flexibility back in the transition from MS-DOS. They just wanted to manage data, and they didn’t want to worry about how to present every little pixel on the screen. Visual Basic provided the tools to write applications quickly and with much less effort than that required by other Windows development tools and languages.
Visual Basic’s simplicity was embraced by developers everywhere, but the honeymoon quickly wore off. Given the speed at which programs of reasonable quality could be cranked out with Visual Basic, programmers and businesses began demanding more. And Microsoft responded. Visual Basic 2.0 and 3.0 were released in quick succession in 1992 and 1993, providing enhanced database integration and additional visual development features. Version 4.0, released in 1996, introduced 32-bit programming to the language, and support for the already-popular Windows 95 platform. Two more quick releases—Visual Basic 5.0 in 1997 and Visual Basic 6.0 in 1998—added even more features and complexity to the otherwise “basic” language, features supporting some but not all object-oriented programming (OOP) techniques, ActiveX control development, and web-based logic coding. Microsoft had even integrated the core Visual Basic engine—christened Visual Basic for Applications, or VBA—into its suite of Office products, proclaiming it as the new official macro language and making the engine available to any third party that wanted to do the same.
Seven years after its initial introduction, Visual Basic had taken the programming world by storm. Millions of developers were using the language, including in-house developers at Fortune 500 companies, writing applications that supported core business functions. VB still retained some of the flavor of the original BASIC language—a “beginner’s” programming language developed by John Kemeny and Thomas Kurtz at Dartmouth College back in 1963. This caused no end of snickering from C and C++ developers and other cola addicts. But VB programmers could see a powerful future for their language of choice.
Then the unthinkable happened. Microsoft announced that it would no longer enhance the core Visual Basic engine. Instead, it would rewrite and reimplement Visual Basic using its soon-to-be-released .NET development platform. Yes, Visual Basic would be endowed with all the power promised for Microsoft’s new C- and Java-like language, C#. But for many hardcore VB developers, it was wrong, just wrong. Words were exchanged. Petitions were crafted. Letters to the editor sounded the call to the Visual Basic faithful, urging them to never write a single line of Visual Basic .NET code, ever. In frustration, a Visual Basic user’s group set fire to the entire Microsoft campus in Redmond.
Well, that didn’t happen. In fact, nothing bad happened at all. Visual Basic .NET turned out to be a software wunderkind, providing power and features that far surpassed anything available in Visual Basic 6.0. Its initial release in 2002 was proof that. Visual Basic .NET 2002 was powerful, but it was also a little hard to use, at least compared with version 6.0, and especially when compared with the original 1.0 product. Visual Basic .NET 2003, released just a year later (obviously), was a relatively minor update with not much in the way of new or easier functionality.
Visual Basic 2005 marked a return to the simpler days of Visual Basic development, days of harmony and peace between “newbies” and their general-purpose programming language. Not only did Microsoft remove the term “.NET” from the product name, but it also removed some of the barriers that kept entry-level programmers from approaching the language. Pre-.NET features, such as Edit and Continue and the display of forms through the simple use of the form’s name, once again found their way into the language and into the hearts of software engineers. Visual Basic still retained all the power it gained with .NET, but with true improvements in usability. It was like when they add a label to your toothpaste that says, “New package, same great regular flavor!” Except that Visual Basic’s flavor was improved, too. Visual Basic was once again accessible to first-time developers.
Since the 2005 release, Microsoft hasn’t just been sitting on its laurels, as painful as that would be. It dug into its bag of tricks with both hands and came out with Visual Basic 2008, the latest VB offering. Formerly code-named Orcas, Visual Basic 2008 brings additional power and simplicity—yes, both of those—to the language. The biggest new feature, LINQ, makes data access easier by letting you tell the system what data you want instead of detailing how to obtain that data. The language also provides more direct access to new technologies such as Ajax and the Windows Presentation Foundation (WPF). But enough fawning. Let’s start learning about the language.
As a general-purpose development language, Visual Basic includes gobs of features that allow you to develop just about any type of application supported by the Microsoft Windows platform. As such, all of its features could never be covered in a concise, 20- or 30-page chapter, and I won’t try. What I will do in this chapter is to introduce you to the basics of the language, and its core features. Features not covered in this chapter are discussed throughout the rest of the book. It has to be that way, since I don’t want you to finish this chapter and then say to yourself, “That Tim Patrick is so amazing. I learned all I needed to know about Visual Basic in one chapter; I didn’t even have to read the rest of the book.” My publisher would not be amused.
In the remainder of this chapter, I will take the “from the inside out” approach, starting the discussion with the core concepts of logic and data, and adding layer after layer of Visual Basic functionality as you turn the pages.
Lest you forget it, let me remind you again: computers are not really very smart. They know how to do only the simplest of tasks. If you want them to do anything remotely complex, you have to give precise, step-by-step instructions down to moving individual bits of data—only 1s and 0s, remember—around in memory. Fortunately, most of the code you would ever need at that low level has already been written for you, and incorporated into the Windows operating system and the .NET Framework. Microsoft- and third-party-supplied code libraries give you a lot of prewritten functionality that’s available for use in your own programs. And that’s good, because you would rather be hurtled into space on a giant bungee cord than have to write business applications at the machine code level all day long.
Even though you have all this great prewritten code in your arsenal, you still have to tell the computer precisely what you want it to do, in fine detail, or it won’t do it. And that’s where high-level languages like Visual Basic come in. They provide the grammar you need to communicate with the computer. For any given tasks that the computer needs to perform, your job as a programmer is to determine the individual steps to accomplish that task—the logic—and translate those steps into computer-ese using the programming language.
As an example, let’s say you receive a request from the sales department for a program that will reverse all the letters in any chunk of text provided to the program. “Our customers are clamoring for this; we need it by Tuesday,” they say. OK, so first you figure out the logic, and then you implement it in Visual Basic. Using pseudocode, an artificial programming language that you make up yourself to help you write programs, you can sketch out the basics of this task (with leading line numbers):
01 Obtain the original text (or string
) from the user.
02 If the user didn't supply any content, then quit now.
03 Prepare a destination for the reversed string, empty for now.
04 Repeat the following until the original string is empty:
05 Copy the last character from the remaining original string.
06 Put that character onto the end of the destination string.
07 Shorten the original string, dropping the last character.
08 [End of repeat section]
09 Show the user the destination string.
You could write this logic in many ways; this is just one example. You can now convert this pseudocode into your language of choice; in this case, Visual Basic (don’t worry about the syntax details for now):
01 originalText = InputBox("Enter text to reverse.") 02 If (Len(originalText) = 0) Then Return 03 finalText = "" 04 Do While (originalText <> "") 05 oneCharacter = Right(originalText, 1) 06 finalText &= oneCharacter 07 originalText = Left(originalText, _ Len(originalText) - 1) 08 Loop 09 MsgBox("The reverse is: " & finalText)
This source code is now ready to be used in a Visual Basic program. And it also demonstrates several essential aspects of coding:
The individual steps of the step-by-step instructions are called statements. In Visual Basic, each statement appears on a line by itself. You can break long statements into multiple lines by connecting the lines with a space-underscore pair, as shown in line 07 of the code. When a single statement is spread across multiple lines in this manner, the entire statement is sometimes called a logical line. Since a single logical line often includes only a single primary Visual Basic action (such as the If
or Do
action, or the various assignment actions using the equals sign [=
]), these actions are also referred to as statements.
The statements of the code are processed one at a time, from top to bottom. However, certain statements alter the normal top-to-bottom flow of the program, as is done with the Do While...Loop
block on lines 04 and 08 of the sample code. Such statements are called flow control statements, and include loops (repeating a block of code), conditions (optionally processing a block of code based on a comparison or calculated result), and jumps (moving immediately to some other section of the code).
Data can be stored in variables, which are named containers for data values. The sample code block includes three variables: originalText
, oneCharacter
, and finalText
, all of which store text (string) data. The .NET Common Type System (CTS) allows you to create variables for four primary types of basic data values: text (both single characters and longer strings), numbers (both integer and decimal values), dates (and times), and Booleans (true or false values). You can also build more complex types of data by grouping the basic types.
Data is stored in a variable through an assignment. Generally, this involves placing a variable name on the left side of an =
assignment operator, and putting the data or calculation to store in that variable on the right side of that same equals sign. The statement finalText = ""
on line 03 stores an empty string (""
) in the variable finalText
. The &=
assignment statement on line 06 shows a slightly different assignment syntax.
Statements can include function calls, blocks of prewritten functionality, all squished down into a single name. Function calls do a bunch of work, and then return a final result, a data value. Function names are followed by a set of parentheses, which may include zero or more arguments, additional data values supplied by the calling code that the function uses to generate its result.
The sample code includes many examples of function calls, including the Right
function on line 05. This function returns a copy of the rightmost characters from another text string. It accepts two parameters: the original string from which to extract the rightmost characters, and an integer value indicating the number of characters to return. The code Right(originalText, 1)
returns a copy of the rightmost single character (1
) from originalText
.
When you use a function in your source code, the function acts a little like a variable; all the text of the function call, from the start of its name to the end of its closing parenthesis, could be replaced by a variable that contained the same resulting data. Function calls cannot appear on the lefthand side of an assignment statement, but they can appear almost anywhere else that a variable can appear. For example, the following two lines could be used to replace line 02 in the sample:
' Replacing --> If (Len(originalText) = 0) Then Return lengthOfText = Len(originalText) If (lengthOfText = 0) Then Return
In addition to functions, Visual Basic also includes procedures. Procedures bundle up prewritten code in a named package, just like functions, but they don’t return a value. They must be used as standalone statements; you cannot use them where you would use a variable or a function call. The call to MsgBox
on line 09 is a typical example of a procedure call in use. (MsgBox
is actually a function, but in this code it is masquerading as a procedure; more on that later.)
The sample code listed previously could be made a little more efficient. In fact, it’s entirely possible that Microsoft obtained an early draft of this book, since it included a string-reversal feature right in Visual Basic, and called it StrReverse
:
originalText = InputBox("Enter text to reverse.") If (Len(originalText) = 0) Then Return finalText = StrReverse(originalText) MsgBox("The reverse is: " & finalText)
That’s right; Visual Basic already includes a string-reversal feature, some of that prewritten library code I keep talking about. Visual Basic includes many such intrinsic functions that are considered part of the language, and that bundle up useful prewritten functionality. Many of these functions appear in the Microsoft.VisualBasic
namespace, which is automatically made available to your Visual Basic source code when you create a new VB project.
Take my data . . . please! Ha, ha, that one always cracks me up. But it’s actually what I ask my Visual Basic application to do: take data from some source (keyboard, hard disk, Internet, etc.) and present it in some useful way. All programs I write will actively manage at least some data in memory. Each data value is stored in a specific area of the computer’s memory, as determined by the Common Language Runtime (CLR). The statements in Visual Basic exist primarily to manage and manipulate this data in useful and complex ways.
All data managed by the CLR is stored in the computer’s memory, with each data value separated and protected from all others. It’s as though each data value had its own individual teacup, as in Figure 2-1.
All data values managed by the CLR have content and type. Content is the actual data: the text string “abc,” the number 5, a sales invoice, orange pekoe. Whatever you put in the teacup, that’s the content. In some cases, .NET allows you to store absolutely nothing in the teacup (for reference types as described shortly, or “nullable” value types as described in Chapter 6).
Type indicates the kind of content stored in the teacup. In Figure 2-1, this is shown by the shape of each teacup. Each teacup has limits on the type of data that can be poured into the teacup: a text string, an integer number, a customer invoice.
Some basic data values, such as numbers and text strings, can be entered into your source code and used just as they are. For instance, the MsgBox
procedure displays a window with a supplied text message. The statement:
MsgBox("The answer is " & 42)
includes a literal string, “The answer is,” and a literal integer value, 42. (The “&” symbol is an operator that connects two values together into a new string.) Literals are used once, and then they’re gone. If I wanted to show the same “The answer is 42” message again, I would have to once again type the same literal values into a different part of the source code.
Visual Basic supports several types of basic literals. String literals are always surrounded by quote marks. If you want to include a quote mark itself in the middle of a string, include two instead of one:
"This is ""literally"" an example."
String literals can be really, really long, up to about 2 billion characters in length; if you were to type just one character per second, it would take more than 63 years to reach the maximum string length. Visual Basic also includes a character literal that is exactly one character in length; if you were to type just one character per second, well, never mind. These character literals are recognized by the “c” trailing after the string. The character literal “A” is entered as:
"A"c
Date and time literals are surrounded by number signs instead of quote marks. The date or time (or both) that you include can be in any format recognized by Microsoft Windows in your specific region. If you are using Visual Studio, it will reformat your date when you type in the literal:
#7/4/1776#
Eleven different kinds of numeric data values—both integers and floating-point values—make up the “core” set of numeric teacups. And who needs more than 11? With these 11 teacups, you can manage numbers from zero all the way to 1 × 10300 and beyond. To use a numeric literal, type the number right in your code, like 27
, or 3.1415926535
. Visual Basic also lets you specify which of the 11 numeric teacups to use for a number, by appending a special character to the end of the number. Normally, 27 is an integer 27. To make it a currency-focused “decimal,” append an at sign (@
):
27@
When I talk about data types in full detail in Chapter 6, I will list the different special characters, like @
, that set the data type for literal numbers.
The fourth and final type of Visual Basic literal is the Boolean literal. Boolean values represent the simplest type of computer data: the bit. Boolean values are either true or false, on or off, yes or no, delicious or disgusting, cats or dogs, zero or nonzero. Booleans always represent any two opposite values or states. Back in the 1800s, George Boole invented Boolean algebra, a language he used to represent logic statements as mathematical equations. It just so happens that computers love Boolean algebra. All the basic operations of a computer, such as addition, are implemented using Boolean functionality.
Visual Basic includes the Boolean literals True
and False
. No quotes; no number signs—just the words True and False. Question: is Tim Patrick telling the truth about this? Answer:
True
In certain cases, you can treat numbers as Boolean values. I’ll talk about it more later on, but for now just know that False
equates to zero (0), and True
equates to everything else (although generally, −1 is used for “everything else”).
Literal data values are all well and good, but they are useful only once, and then they’re gone. Each time you want to use a literal value, you must retype it. It’s as though the data values are stored in disposable cups instead of fine china teacups. And besides, only programmers enter literal values, not users, so they are of limited use in managing user data.
Variables are not simply disposable cups; they are reusable. You can keep putting the same type of tea over and over into the teacup. A string variable teacup can hold a string for reuse over and over. For instance, in this block of code, response
holds the various strings assigned to it:
01 response = "A" 02 MsgBox("Give me an 'A'!") 03 MsgBox(response) 04 MsgBox("Give me another 'A'!") 05 MsgBox(response) 06 MsgBox("What's that spell?") 07 response = StrDup(2, "A") 08 MsgBox(response)
The variable response
is assigned twice with two different strings: an “A” (line 01) and then “AA” (line 07). It keeps whatever value was last assigned to it; both lines 03 and 05 display “A” in a message box window. And you don’t have to assign just literal strings to it; anything that generates a string can assign its result to response
. Line 07 uses a built-in Visual Basic function, StrDup
, to return the two-character string “AA” and assign it to response
.
Using variables is a two-step process. First you must declare the variable, and then you assign a value to it. The Dim
statement takes care of the declaration part; it lets you indicate both the name and the type of a variable. Its basic syntax is pretty straightforward:
Dim response As String
where response
is the name of the variable and String
is its type. Assignment occurs using the =
assignment operator:
response = "The answer"
A single variable can have new values assigned to it over and over again. For those times when you want your variable to have some specific value immediately upon declaration, you can combine declaration and assignment into a single statement:
Dim response As String = "The answer"
Of course, you’re not limited to just a single declaration; you can create as many variables as you need in your code. Each one normally uses its own Dim
statement:
Dim question As String Dim answer As String
You also can combine these into a single statement, although I think it’s just plain ugly:
Dim question As String, answer As String
See, I told you it was ugly. This is just the start of what’s possible with the Dim
statement. I’ll get into more details as the chapter progresses.
I talked about value types and reference types in Chapter 1. Value type variables store an actual value; the tea in a value type teacup is the content itself. All of the literal data values I mentioned previously, except for Strings, are value types.
Reference type variables store a “reference” to the actual data, data found somewhere else in memory. When you look into a reference type teacup, you have to read the tea leaves at the bottom to determine where the real data resides.
Either reference types have data or they don’t. In the absence of data, a reference type has a value of Nothing
, a Visual Basic keyword that indicates no data. Value types are never Nothing
; they always contain some value, possibly the default value for that type (such as zero for numeric types). A special “nullable” type does let you assign Nothing
to a value type, allowing you to implement the same “is there any data here at all” logic that exists with reference types. I’ll talk about nullable types in Chapter 6.
The String
data type is useful, but it’s only one of the teacup shapes at your disposal. The .NET Framework defines several core data types. Each data type is implemented as a specific class within the System
namespace. The most basic data type, a large teacup that can hold any type of data, is called Object
. More than just an object, this is object with a capital O. In the .NET Class Library namespace hierarchy, it’s located at System.Object
. It’s the mother of all classes in .NET; all other classes, structures, enumerations, and delegates, no matter where they reside in the namespace hierarchy, whether they are written by Microsoft or by you, derive from System.Object
. There’s no getting around it; you cannot create a type that ultimately derives from anything else.
So, back to these “core” data types I’ve been hinting at. They match the four types of literal data values I listed before: strings, dates, numbers, and Boolean values. Table 2-1 lists these core data types. Each type also has a Visual Basic-specific name that you can (and should) use instead.
Table 2-1. Core .NET and Visual Basic data types
VB name | .NET name | Description |
---|---|---|
[a] | ||
|
| The |
|
| A numeric data type, |
|
| The |
|
| This date and time data type handles all dates between January 1, 1 AD and December 31, 9999 AD, in the Gregorian calendar. The time can be included as well; if no time is specified, midnight is used. Internally, the |
|
| The |
|
| The |
|
| The |
|
| The |
|
|
|
|
| A numeric data type, |
|
| The |
|
| The |
|
| The |
|
|
|
|
|
|
|
|
|
[a] This is true only in Visual Basic. In other .NET languages, such as C#, |
The Microsoft developers in charge of Visual Basic data types lucked out on that job since all core Visual Basic data types are simply wrappers for specific data types implemented by .NET. The Visual Basic names given for each of these core data types are fully interchangeable with the .NET names. For example, Integer
is fully equivalent to System.Int32
. In fact, when writing Visual Basic code, it is better to use the Visual Basic synonyms, since most Visual Basic developers expect these data type names in the code they read and write.
Except for Object
and String
, all of these data types are value types. All value types are derived from System.ValueType
(which in turn derives from System.Object
).
The SByte
, UInteger
, ULong
, and UShort
data types were added to Visual Basic with its 2005 release, although their System
namespace equivalents have been in .NET since its inception. Unlike the other core data types, these four types are not “CLS-compliant”; that is, they cannot be used to interact with .NET components and languages that limit themselves to just the very core required features of .NET. Generally this is not much of a limitation, but be on your guard when working with third-party components or languages.
When I mentioned the need for declaration and assignment of variables, I was really focusing on value types. Reference types require one additional step: instantiation. Consider the following declaration statements:
Dim defaultValue As Integer Dim nonDefaultValue As Integer = 5 Dim defaultReference As Object
These lines declare three separate variables: two value types (the Integer
s) and one reference type (the Object
). Although only one variable has an explicit data assignment, all three have actually been assigned something, either explicitly or implicitly. Let’s look at those statements again and see what is truly being assigned to each variable.
Dim defaultValue As Integer= 0
Dim nonDefaultValue As Integer = 5 Dim defaultReference As Object= Nothing
Both declaration and assignment already occurred for all the variables, just by using the Dim
statement. The defaultValue
variable, with its default assignment of 0
, can be used immediately in equations. However, the reference type variable defaultReference
is just an empty teacup, with no default data to manipulate. There are features in Visual Basic that let you compare a reference type with Nothing
, and you could do this immediately, but it’s not really data. And remember, variables live to manage data.
Reference data values need instantiation, and instantiation needs the New
keyword:
Dim defaultReference As Object = New
Object
Now defaultReference
points to a real object; now the defaultReference
teacup has something consumable inside it, although since it is just System.Object
, it doesn’t have much in the way of flavor. Strings are a little more interesting, and they also have more interesting constructors.
As you may recall from way back in Chapter 1, a constructor is a block of initialization code that runs when you create a new data value or object. Some objects allow you to supply extra information to a constructor, additional information that is used in the initialization process. A default constructor doesn’t allow you to supply any extra information; it just works on its own, initializing data like it was nobody’s business. There is no limit on the number of constructors in a class, but each one must vary in the type of extra information passed to it.
So, back to Strings. The default constructor for a string simply creates a blank, zero-length string:
Dim worldsMostBoringString As String = New String
Now, nobody ever does this, since the following statement works just as well:
Dim worldsMostBoringString As String = ""
That’s because Strings are treated specially by Visual Basic. String literals are actually instantiations of String
data values; it’s as though you created a new String
instance using the System.String
class. At least that’s true when using the String
data type’s default constructor. But String
also has more interesting constructors. (I’ll delve into the details of constructors in Chapter 8.) One of the constructors creates a new String
instance initialized with a specific character repeated a number of times. For instance, to create a String
instance with a 25-character string of the letter M, use the following syntax:
Dim mmGood As String = New String("M"c, 25)
If you’re going to use the same data type just after the As
keyword that you use right after the New
keyword, you can use a collapsed syntax:
Dim mmGood As New String("M"c, 25)
As with value types, you can also break the statement into distinct declaration and assignment statements:
Dim mmGood As String mmGood = New String("M"c, 25)
Literals don’t change, but you can use them only once in your code. Constants are a cross between a literal and a variable; they have a single, never-changing value just like data literals, but they also have a name that you can use over and over again, just like variables.
You declare constants using the Const
keyword instead of the Dim
keyword:
Const SpeedOfLight As Integer = 186000
Actual assignment of the value to the constant occurs in the statement itself, with the value following the =
operator. Once your constant is declared and assigned, it’s available for use in actual statements of your actual code:
MsgBox("Lightspeed in miles/second: " & SpeedOfLight)
In the real world, you need to keep some data private, for your use only. Your neighbors have other juicy bits of data and information that they share among themselves. And then there is public data that isn’t hidden from anyone. But it’s not just this way in the real world; the fake world of Visual Basic has different levels of access and privacy for your data.
A little later in the chapter, we’ll see that your application’s logic code will always appear in procedures, named blocks of source code. You declare local variables (and constants) in these same procedures when you need a short-lived and personal variable that is only for use within a single procedure. Other variables (and constants) can appear outside procedures, but still within the context of a class or similar type. These fields, whether variable or constant, are immediately available to all the different procedures that also call the current class home.
You define all local variables using the Dim
keyword. The Dim
statement works for field definitions, but it’s more common to use special access modifier keywords instead. These modifiers determine what code can access the fields, from Private
(used only by code inside the class) to Public
(also available outside the class):
Private ForInClassUseOnly As Integer
There are five access modifiers in all. I’ll talk more about them and about fields in general in Chapter 6.
That was a lot to take in. Getting your mind around data and variables is probably the most complex part of programming in Visual Basic. Once you have the data in variables, it’s pretty easy to manipulate.
Although the thought of a cup of tea may cause you to run out of the room like a raving lunatic, you might want to take a few minutes, grab a cup, glass, saucer, or mug of your beverage of choice, and relax. I’ll see you in about 20 or 30 minutes.
If you’re an opera fan, you know how exciting a good opera can be, especially a classic work presented with the original foreign language libretto. If you’re not an opera fan, you know how irritating it can be to listen to several hours of a foreign language libretto. With the advent of “supra titles” conveying the English-language interpretation of the content, those who until now have gotten little joy out of the opera experience will still find it repulsive, only this time in their native tongue. But at least now they will know why they don’t enjoy the story.
That’s really what comments do: tell you in your own language what is actually going on in a foreign language. In this book, the foreign language is Visual Basic, and English is the vernacular. You may find a particular block of Visual Basic code to be poorly written or even detestable, but if the accompanying comments are accurate, you can be disgusted in your own language, with a human-language understanding of the process.
Comments normally appear on lines by themselves, but you may also attach a comment to the end of an existing code line. If a logical line is broken into multiple physical lines using the “_” line continuation character, a trailing comment is valid only at the end of the final physical line:
' ----- This is a standalone comment, on a line by itself. Dim counter As Integer ' This is a trailing comment. MsgBox("The counter starts at " & _ ' INVALID COMMENT HERE! counter) ' But this one is valid.
Comments begin with the comment character, the standard single quote character ('
). Any text following the comment character is a comment, and is ignored when your code is compiled into a usable application. Any single quote that appears within a literal string is not used as a comment marker.
MsgBox("No 'comments' in this text.")
Comments can also begin with the REM
keyword (as in “REMark”), but most programmers use the single-quote variation instead.
A few code examples ago you saw that Visual Basic would supply a default assignment to a variable—at least for value types—if you neglected to include one. In certain cases, Visual Basic will also supply the declaration if you leave it out. In the statement:
brandNewValue = 5
if there is no related Dim
statement that defines brandNewValue
, Visual Basic will declare the variable on your behalf, assigning it to the Object
data type. Don’t let this happen to you! You don’t know what kind of trouble you will have if you allow such practices in your code. You will quickly find your code filled with mysterious logic bugs, esoteric data issues, recurrent head lice, and so on.
The problem is that Visual Basic will not complain if you mistype the name of your auto-declared variable. Left unchecked, such practices could lead to code such as this:
brandNewValue = 5 MsgBox(brandNewVlaue)
My, my, my, look at that spelling mistake on the second line. What? Visual Basic compiled without any error? And now your message box displays nothing instead of 5? You could avoid such trauma by judicious use of the Option
statements included in the Visual Basic language. There are four such statements:
Option Explicit On
This statement forces you to declare all variables using Dim
(or a similar statement) before use. It’s possible to replace “On” with “Off” in the statement, but don’t do it.
Option Strict On
Visual Basic will do some simple data conversions for you when needed. For instance, if you assign a 64-bit Long
data value to a 32-bit Integer
variable, Visual Basic will normally convert this data to the smaller size for you, complaining only if the data doesn’t fit. This type of conversion—a narrowing conversion—is not always safe since the source data will sometimes fail to fit in the destination. (A widening conversion, as with storing Integer
data in a Long
, always works, since the destination can always hold the source value.) The Option Strict On
statement turns off the automatic processing of narrowing conversions. You will be forced to use explicit conversion functions to perform narrowing conversions. This is good, since it forces you to think about the type of data your variables will hold. You can replace “On” with “Off” in this statement, but if I’ve warned you once, I’ve warned you twice: don’t even try it.
Option Infer On
This new Visual Basic 2008 statement tells the compiler to make a guess at which data type you want a variable to use when you don’t specifically tell it. I discuss type inference in Chapter 6, so I’ll delay the details for now. Usually you will want to keep this option set to “On.”
Option Compare Binary
and Option Compare Text
These two variations of the Option Compare
statement instruct your code to use specific sorting rules for certain string comparison features. In general, Binary
comparisons are case-sensitive, whereas Text
comparisons are not. It’s up to you which method you want to use; the default is Binary
.
These statements appear at the top of each source code file in your project, before any other code:
Option Explicit On Option Strict On
Or, to save on precious disk space, set default values that apply to your entire project through the project’s properties. In Visual Studio, select the Project → Properties menu command. On the project’s properties window that appears, select the Compile tab, and set your default choices for the “Option explicit,” “Option strict,” “Option compare,” and “Option infer” fields (see Figure 2-2).
Visual Basic includes several basic operators that let you do what your code really wants to do: manipulate data. To use them, just dial zero from your phone. No, wait; those operators let you place operator-assisted calls for only $2.73 for the first minute. The Visual Basic operators let you perform mathematical, logical, bitwise, and string management functions, all at no additional cost.
The most basic operator is the assignment operator, represented by the equals sign (=
). You’ve already seen this operator in use in this chapter. Use it to assign some value to a variable (or constant); whatever appears to the right of the operator gets assigned to the reference type or value type variable on the left. The statement:
fiveSquared = 25
assigns a value of 25
to the variable fiveSquared
.
Most operators are binary operators—they operate on two distinct values, one to the operator’s left and one to the right; the result is a single calculated value. It’s as though the calculation is fully replaced by the calculated result. For instance, the addition operation:
seven = 3 + 4
becomes:
seven = 7
before the final application of the assignment (=
) operator. A unary operator appears just to the left of its operand. For instance, the unary negation operator turns a positive number into a negative number:
negativeSeven = −7
I’ll comment on each operator in detail in Chapter 6. But we’ll need a quick summary for now so that we can manipulate data before we get to that chapter. Table 2-2 lists the main Visual Basic operators and briefly describes the purpose of each one.
Table 2-2. Visual Basic operators
Operator | Description |
---|---|
[a] | |
| |
| The unary plus operator retains the sign of a numeric value. It’s not very useful until you get into operator overloading, something covered in Chapter 12. |
| The subtraction operator subtracts the second operand from the first. |
| The unary negation operator reverses the sign of its associated numeric operand. |
| The multiplication operator multiplies two numeric values together. |
| The division operator divides the first numeric operand by the second, returning the quotient including any decimal remainder. |
The integer division operator divides the first numeric operand by the second, returning the quotient, but with the decimal remainder truncated. | |
| The modulo operator divides the first numeric operand by the second, and returns only the remainder as an integer value. |
| The exponentiation operator raises the first operand (the base) to the power of the second (the exponent). |
| The string concatenation operator joins two string operands together, and returns a new string with the combined results. |
| The conjunction operator returns |
| This operator is just like the |
| The disjunction operator returns |
| This operator is just like the |
| The negation operator returns the opposite of a Boolean operand. |
| The exclusive or operator returns |
| The shift left operator shifts the individual bits in an integer operand to the left by the number of bit positions in the second operand. |
| The shift right operator shifts the individual bits of an integer operand to the right by the number of bit positions in the second operand. |
| The equal-to comparison operator returns |
| The less-than comparison operator returns |
| The less-than-or-equal-to comparison operator returns |
| The greater-than comparison operator returns |
| The greater-than-or-equal-to comparison operator returns |
| The not-equal-to comparison operator returns |
| The pattern comparison operator returns |
| The object equal-to comparison operator returns |
| The object not-equal-to comparison operator is the opposite of the |
As powerful as operators are, they possess even more power when you combine them. This works because any of the operands can be a complex expression that includes its own operands. Parentheses grouped around clauses in operands ensure that values are processed in the order you expect.
circleArea = pi * (radius ^ 2)
In this statement, the second operand of the *
multiplication operator is another expression, which includes its own operator.
Years ago I worked for a software company that sometimes published software developed outside the organization, all for a non-Windows platform. While most of these programs were written in the C language, we also published software written in Pascal, assembly language, and good ol’ BASIC. I inherited one such external application written entirely in BASIC, a program that assisted the user in 3D modeling and graphics rendering. It was a complex program, containing about 30,000 lines of source code. The problem was that it was one large block of 30,000 source code lines. No comments, no variable names longer than a few characters, no extra-strength buffered aspirin product. Just thousands of lines of code with flow control statements jumping this way and that. And, of course, it had a bug.
I was able to move past that event in my life without too much therapy, but at the time it was a shock to see code in that condition. And it was so unnecessary, since that flavor of BASIC was a procedural language, just like C and Pascal. Procedural languages allow you to break your code into named blocks of logic, called procedures. These procedures let you take a “divide and conquer” approach to programming; you write procedures that accomplish a specific logical portion of the code within your entire application, and then access these procedures from other procedures.
Visual Basic includes three types of procedures:
These procedures, also called subprocedures, do a bunch of work and then return to the calling procedure. Data can be sent into the subroutine through its argument list, and some values may come back through that same list, but the procedure does not send an official final result back. A subroutine does its work, and once it is complete, the calling code continues on its merry way.
Functions are just like subroutines, with one additional feature: you can return a single value or object instance from the function as its official result. Usually, the calling code takes this return value into consideration when it completes its own logic.
When used, properties actually look like variables. You assign and retrieve values to and from properties just like you would for a variable. However, properties include hidden code, often used to validate the data being assigned to the property.
Subroutines, functions, and properties are the code members of each class or similar type. I’ll delay discussion of properties until a little later in the chapter. For now, let’s enjoy functions and subroutines, which together are also known as methods. Let’s start with subroutines. To call a subroutine, type its name as a statement, followed by a set of parentheses. Any data you need to send to the subroutine goes in the parentheses. For instance, the following subroutine call does some work, passing the ID number of a customer, and a starting date:
DoSomeWork(customerID, startDate)
Each subroutine defines the data type and order of the arguments you pass. This argument list may include one or more optional arguments, which are assigned default values if you don’t include them. A subroutine might also be overloaded, defining different possible argument lists based on the number and data type of the arguments. We’ll encounter a lot of these later.
Functions are a little more interesting since they return a usable value. Often, this value is assigned to a variable:
Dim balanceDue As Boolean balanceDue = HasOutstandingBalance(customerID)
Then you can do something with this result. If you want, you can ignore the return value of a function, and we already have. The MsgBox
function used earlier returns the identity of the on-screen button clicked by the user to close the message box. If you include only an OK button (the default), you probably don’t care which button the user clicks.
MsgBox("Go ahead, click the OK button.")
But you can also capture the result of the button:
whichButton = MsgBox("Click Yes or No.", MsgBoxStyle.YesNo)
In this case, whichButton
will be either MsgBoxResult.Yes
or MsgBoxResult.No
, two of the possible results defined by the MsgBox
function.
Sometimes you have to make some choices, and conditional expressions will help you do just that. Visual Basic includes support for conditions, which use data tests to determine which code should be processed next.
The most common conditional statement is the If
statement. It is equivalent to English questions in the form “If such-and-such is true, then do so-and-so.” For instance, it can handle “If you have $20, then you can buy me dinner,” but not “If a train departs Chicago at 45 miles per hour, when will it run out of coal?”
If
statements have syntax that spans multiple source code lines:
01 If (hadAHammer = True) Then 02 DoHammer(inTheMorning, allOverThisLand) 03 DoHammer(inTheEvening, allOverThisLand) 04 ElseIf (hadAShovel = True) Then 05 DoShovel(inTheNoontime, allOverThisLand) 06 Else 07 TakeNap(allDayLong, onMySofa) 08 End If
The If
statement lets you define branches in your code based on conditions. It is built from three main components:
The expression found between the If
(or ElseIf
) keyword and the Then
keyword is the condition. The sample includes two conditions, on lines 01 and 04. Conditions may be simple or complex, but they must always result in a Boolean True
or False
value. They can include calls to other functions and multiple logical and comparison operators.
If ((PlayersOnTeam(homeTeam) >= 9) And _ (PlayersOnTeam(visitingTeam) >= 9)) Or _ (justPracticing = True) Then PlayBall( ) Else StadiumLights(turnOff) End If
The original condition always follows the If
keyword. If that conditions fails, you can specify additional conditions following an ElseIf
keyword, as on line 04. You may include as many ElseIf
clauses as you need. The optional Else
condition doesn’t let you specify a test expression. Instead, it matches everything not yet caught by the If
or ElseIf
clauses. Only one Else
clause is allowed per If
statement.
Each condition’s Then
keyword is followed by one or more Visual Basic statements that are processed if the associated condition evaluates to True
. All statements up to the next Else
, ElseIf
, or End If
are included in that branch’s statement block. You may include any number of statements in a branch block, including additional subordinate If
statements. In the sample code, branch lines 02 and 03 are processed if the original hadAHammer
condition is true. Line 05 is processed instead if the original condition fails, but the second hadAShovel
condition passes. If none of the conditions is True
, the Else
’s branch, on line 07, executes.
The If
statement is one of several multiline statements in Visual Basic, all of which end with the keyword End
followed by the original statement keyword (If
in this case). The If
statement’s keywords, which give the statement its structure, include If
, Then
, ElseIf
, Else
, and End If
. All ElseIf
and Else
clauses and related branches are optional. The simplest If
statement includes only an If
branch.
If (phoneNumberLength = 10) Then DialNumber(phoneNumber) End If
For conditions with simple single-statement branches and no ElseIf
clauses, a single-line alternative can keep your code looking clean.
If (SaveData( ) = True) Then MsgBox("Data saved.") If (TimeOfDay >= #13:00#) _ Then currentStatus = WorkStatus.GoHome _ Else currentStatus = WorkStatus.BusyWorking
If
statements are cool because they make your code more than just a boring set of linear step-by-step instructions that never deviate for any reason. Software is written to support some real-world process, and real-world processes are seldom linear. The If
statement makes it possible for your code to react to different data conditions, taking the appropriate branch when necessary.
Once the entire If...End If
block completes, processing continues with the next statement that follows the End If
statement.
Sometimes you might write an If
statement that tests a variable against one possible value, then another, then another, then another, and so on:
If (billValue = 1) Then presidentName = "Washington" ElseIf (billValue = 2) Then presidentName = "Jefferson" ElseIf (billValue = 5) Then presidentName = "Lincoln" ...
And on it goes, through many more ElseIf
clauses. It’s effective, but a little tedious, as your code must specifically test every case. The Select Case
statement provides a cleaner alternative for simple value comparisons against a list:
01 Select Case billValue 02 Case 1 03 presidentName = "Washington" 04 Case 2 05 presidentName = "Jefferson" 06 Case 5 07 presidentName = "Lincoln" 08 Case 20 09 presidentName = "Jackson" 10 Case 50 11 presidentName = "Grant" 12 Case 10, 100 13 presidentName = "!! Non-president" 14 Case > 100 15 presidentName = "!! Value too large" 16 Case Else 17 presidentName = "!! Invalid value" 18 End Select
Unlike the If
statement, which checks for a Boolean result, Select Case
compares a single value against a set of test case values. In the example, the billValue
variable is compared against the different values identified by each Case
clause. All code that follows a Case
clause (until the next Case
clause) is the branch that is processed when a match is found. An optional Case Else
condition (line 16) catches anything that is not matched by any other Case
. Normally, Case
clauses list single values for comparison. They can also include a list of comma-separated comparison values (line 12), or simple range comparison expressions (line 14).
Visual Basic includes two variations of the If
statement for “inline” use. Consider the following statement:
If (gender = "F") Then fullGender = "Female" _ Else fullGender = "Male"
Using the IIf
function, this statement compresses into a single assignment statement with an embedded condition:
fullGender = IIf(gender = "F", "Female", "Male")
The IIf
function has three comma-delimited arguments. The first is the condition, which must result in a Boolean True
or False
value. The second argument is returned by the function if the condition is True
; a condition result of False
returns the third argument. For simple conditions that are destined to return single values to a common variable, it’s really a useful function. But with anything really useful, there are caveats. The caveat with IIf
is that anything appearing inside the IIf
statement will be processed, even if it is not returned as a result. Here’s a dangerous example:
purgeResult = IIf(level = 1, PurgeSet1( ), PurgeSet2( ))
The statement will correctly return the result of either PurgeSet1( )
or PurgeSet2( )
based on the value of level
. The problem, or potential problem, is that both functions, PurgeSet1( )
and PurgeSet2( )
, will be called; if level
is 1
, both PurgeSet1( )
and PurgeSet2( )
will be called, even though only the function result from PurgeSet1( )
will be returned.
To help avoid such side effects, Visual Basic 2008 added the new If
operator. It looks just like the IIf
function, except for the keyword If
replacing the IIf
keyword:
purgeResult = If(level = 1, PurgeSet1( ), PurgeSet2( ))
Now only PurgeSet1( )
or PurgeSet2( )
will be called based on the condition, but not both. Although the If
operator looks like a function, it is actually a true operator, known as the ternary operator. At compile time, Visual Basic treats it and its arguments as operator and operands and generates the appropriate logic.
A variation of the If
operator takes just two arguments, excluding the initial Boolean argument.
realObject = If(object1, object2)
In this version of the If
operator, if the first argument evaluates to Nothing
, the operator returns the second argument. If the first argument is not Nothing
—that is, if it really is something—the operator returns that first argument instead. The goal is to return non-Nothing
, although that’s a double negative.
Visual Basic includes three major types of loops: For...Next
, For Each...Next
, and Do...Loop
. Just as conditions allow you to break up the sequential monotony of your code through branches, loops add to the usefulness of your code by letting you repeat a specific block of logic a fixed or variable number of times.
The For...Next
loop uses a numeric counter that increments from a starting value to an ending value, processing the code within the loop once for each incremented value.
Dim whichMonth As Integer For whichMonth = 1 To 12 ProcessMonthlyData(whichMonth) Next whichMonth
This sample loops 12 times (1 To 12
), once for each month. You can specify any starting and ending values you wish; this range can also be specified using variables or functions that return numeric values. Once the starting and ending values are obtained, they are not recalculated each time through the loop, even if a function call is used to obtain one or both limits.
' ----- Month(Today) returns the numeric month ' for the current date. For whichMonth = 1 To Month(Today) ProcessMonthlyData(whichMonth) Next whichMonth
Normally, the loop increments by one (1
) each time through. You can alter this default by attaching a Step
clause to the end of the For
statement line:
For countDown = 60 To 0 Step −1 ... Next countDown
One additional syntax variation allows you to declare the loop counter variable within the statement itself. Such variables are available only within the loop, and cease to exist once the loop exits.
For whichMonth As Integer
= 1 To 12
ProcessMonthlyData(whichMonth)
Next whichMonth
A variation of the For
loop, the For Each...Next
loop scans through a set of ordered and related items, from the first item until the last. Arrays and collection objects also work, as does any object that supports the IEnumerable
interface (all these topics are covered in Chapter 6). The syntax is quite similar to the standard For
statement:
For Each oneRecord In setOfRecords ProcessRecord(oneRecord) Next oneRecord
Sometimes you want to repeat a block of code as long as a certain condition is true, or only until a condition is true. The Do...Loop
structure performs both of these tasks. The statement includes a While
or Until
clause that specifies the conditions for continued loop processing. For instance, the following statement does some processing for a set of dates, from a starting date to an ending date:
Dim processDate As Date = #1/1/2000# Do While (processDate < #2/1/2000#) ' ----- Perform processing for the current date. ProcessContent(processDate) ' ----- Move ahead to the next date. processDate = processDate.AddDays(1) Loop
Processing in this sample will continue until the processDate
variable meets or exceeds 2/1/2000, which indicates the end of processing. The Until
clause version is somewhat similar, although with a reversed condition result:
Do Until (processDate >= #2/1/2000#) ... Loop
Make the included condition as simple or as complex as you need. Putting the Until
or While
clause at the bottom of the loop guarantees that the statements inside the loop will always be processed at least once:
Do ... Loop Until (processDate >= #2/1/2000#)
If the loop condition is never met, the loop will continue forever. So, if you want your loop to exit at some point (and usually you do), make sure the condition can eventually be met.
There is another loop that is similar to Do...Loop
, called the While...End While
loop. However, it exists for backward compatibility only. Use the Do...Loop
statement instead.
Normally, when you enter a loop, you have every intention of looping for the full number of times specified by the initial conditions of the loop. For For
loops, you expect to continue through the entire numeric range or collection of elements. In Do
loops, you plan to keep the loop going as long as the exiting condition has not yet been met. But there may be loops that you want to exit early. You accomplish this using an Exit
statement.
There are two loop-specific Exit
statements:
Exit For
Exits a For...Next
or For Each...Next
loop immediately
Exit Do
Exits a Do...Loop
statement immediately
Each Exit
statement exits the loop that contains the statement; processing continues with the line immediately following the loop:
For whichMonth = 1 To 12 If (ProcessMonthlyData(whichMonth) = False) Then Exit For Next whichMonth ' ----- Code continues here no matter how the loop was exited.
The sample code is designed to loop through all 12 months. However, a processing failure for any of the 12 months will immediately exit the loop, abandoning all remaining month processing actions. The Exit Do
statement similarly exits Do...Loop
loops immediately.
In an Exit
loop statement within nested loops (where one loop appears within another), only the matching loop that immediately contains the statement is exited:
For whichMonth = 1 To 12 For whichDay = 1 to DaysInMonth(whichMonth) If (ProcessDailyData(whichMonth, whichDay) = False) _ Then Exit For Next whichDay ' ----- The Exit For statement jumps to this line. ' Processing continues with the next month. Next whichMonth
Since exiting a loop abandons all remaining passes through the loop, you may miss out on important processing that would have happened in subsequent passes. Visual Basic includes a Continue
statement that lets you abandon only the current pass through the loop.
There are different Continue
statement variations for each loop type:
Continue For
Immediately jumps to the end of the For...Next
or For Each...Next
loop and prepares for the next pass. The loop variable is incremented and compared with the range or collection limits.
Continue Do
Immediately jumps to the end of the Do...Loop
statement and prepares for the next pass. The Until
or While
condition is reevaluated.
Since the loop conditions are re-evaluated when using the Continue
statements, there are times when Continue
may cause the loop to exit, such as when it had been the final pass through the loop already.
In this example, the Continue For
statement skips processing for months that have no data to process:
For whichMonth = 1 To 12 If (DataAvailable(whichMonth) = False) Then Continue For RetrieveData(whichMonth) ProcessData(whichMonth) SaveData(whichMonth) Next whichMonth
All logic statements in your code must appear within a procedure, whether in a subroutine, a function, or a property. Although there are thousands of prewritten procedures for you to choose from in the .NET Framework libraries, you can also add your own.
Subroutines begin with a Sub
declaration statement and end with an End Sub
statement. All of your subroutine’s logic appears in between these two mighty jaws.
01 Sub ShowIngredients(ByVal gender As Char) 02 Dim theMessage As String = "Unknown." 03 If (gender = "M"c) Then 04 theMessage = "Snips and snails and puppy dog tails." 05 ElseIf (gender = "F"c) Then 06 theMessage = "Sugar and spice and everything nice." 07 End If 08 MsgBox(theMessage) 09 End Sub
Line 01 shows the subroutine’s declaration line in its simplest form; throughout the book, you will find that there are additional keywords that decorate procedure declarations to change their behavior. The statement begins with the Sub
keyword (for subroutine), followed by the name of the procedure, ShowIngredients
.
The parentheses following this name contain the subroutine’s parameters. Parameters allow another block of code that will use this procedure to pass data into the procedure, and optionally receive data back. You can include any number of parameters in the subroutine definition; simply separate them by commas. Each parameter specifies the name as it will be used in the procedure (gender
in the sample) and its data type (Char
). The arguments are treated as declared variables within the procedure, as is done with gender
on lines 03 and 05.
The values supplied by the calling code are known as arguments. All arguments are passed by value or by reference. In the sample code, the argument passed into gender
will be passed by value, as specified through the ByVal
keyword. The related ByRef
keyword indicates an argument to be passed by reference. If you don’t include either keyword, ByVal
is assumed. This passing method impacts whether changes made to the argument within the local procedure are propagated back to the calling code. However, the ability to update the original data is also influenced by whether the data is a value type or a reference type. Table 2-3 indicates the behavior for each combination of passing method and data type.
Table 2-3. Updating data, the .NET way
Passing method | Data type | Behavior |
---|---|---|
| Value type | Changes made to the local version of the argument have no impact on the original version. |
| Reference type | Changes made to members of the data object immediately impact the original data object. However, the object itself cannot be changed or replaced with a completely new data object. |
| Value type | Changes made to the local version are returned to the calling procedure, and permanently impact the original data value. |
| Reference type | Changes made to either the data object or its members are also changed in the original. It is possible to fully replace the object sent into the procedure. |
In most cases, if you are interested in modifying the value of a parameter and having the changes return to the caller, use ByRef
; otherwise, use ByVal
.
Lines 02 through 08 in the sample code comprise the body of the procedure, where all your logic appears. Any variables to be used solely in the routine are also defined here, as with the theMessage
variable on line 02. The subroutine always concludes with an End Sub
statement.
The syntax of a function differs only slightly from subroutines, to support a return value.
01 Function IsPrime(ByVal source As Long) As Boolean 02 ' ----- Determine whether source is a prime number. 03 Dim testValue As Long 04 If (source < 2) Then 05 Return False 06 ElseIf (source > 2) Then 07 For testValue = 2 To source 2& 08 If ((source Mod testValue) = 0) Then 09 Return False 10 End If 11 Next testValue 12 End If 13 Return True 14 End Function
As with subroutines, the function’s declaration line appears first (line 01), followed by the body (lines 02 through 13) and the closing End Function
statement (line 14). The declaration line includes an extra data type definition after the parameter list. This is the data type of the final value to be returned to the calling code. Use this return value in the calling code just like any other value or variable. For example, the following line calls the IsPrime
function and stores its Boolean result in a variable:
primeResult = IsPrime(23)
To indicate the value to return, use the Return
statement (described later in the chapter). The sample code does this on lines 05, 09, and 13. (An older VB 6.0 syntax that lets you assign the return value to the name of the function still works.)
A little earlier I mentioned fields, which are variables or constants that appear within a class, but outside any procedure definition.
01 Class PercentRange 02 Public Percent As Integer 03 End Class
Properties are similar to fields; they are used like class-level variables or constants. But they are programmed like functions, accepting parameters, having return values, and including as much logic as you require.
Properties are often used to protect private class data with logic that weeds out inappropriate values. The following class defines a single property that provides access to the hidden related field:
01 Class PercentRange 02 ' ----- Stores a percent from 0 to 100 only. 03 Private savedPercent As Integer 04 Public Property Percent( ) As Integer 05 Get 06 Return savedPercent 07 End Get 08 Set(ByVal value As Integer) 09 If (value < 0) Then 10 savedPercent = 0 11 ElseIf (value > 100) Then 12 savedPercent = 100 13 Else 14 savedPercent = value 15 End If 16 End Set 17 End Property 18 End Class
The Percent
property (lines 04 to 17) protects access to the savedPercent
field (line 03), correcting any caller-supplied values that exceed the 0 to 100 range. Properties include separate assignment and retrieval components, also called accessors. The Get
accessor (lines 05 to 07) returns the property’s monitored value to the caller. The Set
accessor (lines 08 to 16) lets the caller modify the value of the property.
The property declaration statement (line 04) includes a data type that matches the data type passed into the Set
accessor (line 08). This is the data type of the value set or retrieved by the caller. To use this sample Percent
property, create an instance of the PercentRange
class, and then use the property:
Dim activePercent As New PercentRange activePercent.Percent = 107 ' An out-of-range Integer MsgBox(activePercent.Percent) ' Displays "100", not "107"
You can create read-only or write-only properties by including the ReadOnly
or WriteOnly
keyword just before the Property
keyword in the declaration statement (line 04), and leaving out the unneeded accessor.
Properties do not need to be tied to fields. You can use properties to get and set any type of value, and store it or act upon it in any manner you wish.
Back in the good ol’ days of Visual Basic 6.0, procedures could appear just about anywhere in your source code files. You would open a source file, type a function, and go; it was that easy. With the move to .NET, all Visual Basic procedures must now appear within a defined class (or a structure or module).
Class Employee Sub StartVacation( ) ... End Sub Function TotalVacationTaken( ) As Double ... End Function End Class
When you create instances of your class later in code, the methods can be called directly through the object instance.
Dim executive As New Employee ... executive.StartVacation( )
Chapter 8 shows you how to use and build classes.
The loops and conditional statements available in Visual Basic let you reroute your code based on data. The language includes a few other statements that let you control the action in a more direct manner.
The GoTo
statement lets you jump immediately to some other location within the current procedure. The destination of a jump is always a line label, a named line position in the current procedure. All line labels appear at the start of a logical line, and end with a colon.
PromptUser: GetValuesFromUser(numerator, denominator) If (denominator = 0) Then GoTo PromptUser quotient = numerator / denominator
In this sample, the GoTo
statement jumps back to the PromptUser
label when the code detects invalid data. Processing continues with the line immediately following the PromptUser
label. You can’t use the same label name twice in the same procedure, although you can reuse label names in different procedures. If you want, include another logic statement on the same line as your label, right after the colon, although your code will be somewhat easier to read if you keep labels on their own lines.
LabelAlone: MsgBox("It's all alone.") LabelAndCode: MsgBox("Together again.")
It’s all right to include as many labels in your code as you need, but the GoTo
statement is one of those elements of Visual Basic that is monitored closely by pesky international software agencies, such as the International Committee to Keep GoTo Always Gone (ICK-GAG). That group also scans computer books looking for derogatory references to its organization name—not that it would find anything like that in this book. But its core issue is that overuse of GoTo
statements can lead to spaghetti code, such as the following:
Dim importantMessage As String = "Do" GoTo Step2 Step6: importantMessage &= "AG!" GoTo Step7 Step3: importantMessage &= "wit" GoTo Step4 Step2: importantMessage &= "wn " GoTo Step3 Step5: importantMessage &= "CK-G" GoTo Step6 Step4: importantMessage &= "h I" GoTo Step5 Step7: MsgBox(importantMessage)
Some people say that such code is hard to read. Others call it job security. No matter what you call it, it does make code very hard to maintain and review. You should probably keep an eye on your use of GoTo
statements; if you don’t, someone else might.
Visual Basic itself places some limits on the use of GoTo
. You cannot jump into or out of certain multiline statements that would result in improperly initialized code or data values. For instance, you cannot jump into the middle of a For...Next
statement from outside the statement, since the loop counter variable and the starting and ending ranges would not be properly initialized.
' ----- This GoTo statement will fail. GoTo InsideTheLoop For counter = 1 to 10 InsideTheLoop: MsgBox("Loop number: " & counter) Next counter
However, once you are inside the loop, you can jump to line labels that also appear in the loop, and it’s acceptable to jump out of the loop using GoTo
. Some other multiline structures impose similar restrictions.
Not only can you jump around within a procedure using GoTo
, but you can also jump right out of a procedure anytime you want using the Return
statement. Normally, a procedure exits when processing reaches the last line of code in the procedure; processing then continues with the code that called the procedure. The Return
statement provides a way to exit the procedure before reaching the end.
In subroutines, the Return
statement appears by itself as a standalone statement:
Return
In functions, the statement must include the value to be returned to the calling code: a variable, a literal, or an expression that must match the specified return value data type of the function.
Return 25
Pre-.NET releases of Visual Basic used an Exit
statement to immediately leave a procedure. These are still supported in .NET. There are three variations:
Exit Sub
Exits a subroutine
Exit Function
Exits a function
Exit Property
Exits a property
When exiting from a function, the Exit Function
statement does not include a way to specify a return value. You must set the return value separately by assigning the return value to the name of the function.
Function SafeDivide(ByVal numerator As Double, _ ByVal denominator As Double) As Double ' ----- The "#" sign makes a number a Double. If (denominator = 0#) Then ' ----- Return 0 on invalid division. SafeDivide = 0# Exit Function End If Return numerator / denominator End Function
The End
and Stop
statements bring an immediate halt to your Visual Basic application. The End
statement exits your program immediately, aborting all further code and data processing (although certain acquired resources are cleaned up).
The Stop
statement suspends processing only when you are running your application within a debugger, such as the Visual Studio development environment. Stop
returns control to the environment, allowing the developer to examine and possibly alter data and code before continuing on with the program. If a Stop
is encountered in a standalone application running outside the debugger, it prompts the user to debug the application using any debugger installed on the workstation. Needless to say, the user will not be amused.
Visual Basic is an event-driven language. This is especially true of programs written to run on the Windows desktop. After some important initialization, the user is generally in control of all actions in the program. Who knows what the crazy user will do. He might click here. She might type there. It could be all mayhem and bedlam. But whatever the user does, your program will learn about it through events.
Since the first days of Windows, desktop programs have used a message pump to communicate user and system actions to your code. Mouse and keyboard input, system-generated actions, and other notifications from external sources flow into a program’s common message queue. The message pump draws these messages out one by one, examines them, and feeds them to the appropriate areas of your code.
In traditional Windows programming, you craft the message pump yourself, including code that makes direct calls to event-handling procedures based on the message type. In a Visual Basic program (both in .NET and earlier), the language provides the message pump for you. It analyzes the messages as they are pumped out of the message queue, and directs them to the appropriate code. In .NET, this code appears within classes. Once a class has a chance to analyze the message, it can generate an event, which is ultimately processed by an event handler, a subroutine you write to respond to the action. This calling of the event handler is known as firing an event. So, there are two parts of an event: (1) some code that decides to fire the event; and (2) an event handler that responds to the fired event.
Events are really just indirect calls to a procedure. Instead of having the main code call another subroutine directly, it asks .NET to call the other subroutine for it, passing specific arguments that the calling code may wish to include. So, why would you want to do this instead of just making the subroutine call directly? For one thing, this indirect method lets you add event handlers long after the initial event-firing code was written. This is good, since the event-firing code may be in a third-party assembly that was written years ago. A second benefit is that one event can target multiple event handlers. When the event fires, each event handler will be called, and each can perform any custom logic found in the handler subroutine.
The code that fires the event passes event-specific data to the target event handler(s) through the handler’s parameter list. For the indirect subroutine call to work, the event handler needs to contain the correct number of arguments, in the right order, each of a specific and expected data type. The Event
statement defines this contract between the event and the handler.
Public Event SalaryChanged(ByVal NewSalary As Decimal)
This Event
statement defines an event named SalaryChanged
with a single argument, a Decimal
value. Any event handler wishing to monitor the event must match this argument signature.
Sub EmployeePayChanged(ByVal updatedSalary As Decimal)...
Events can occur for any reason you deem necessary; they need not be tied to user or system actions. In this sample class, an event fires each time a change is made to the employee’s salary. The RaiseEvent
statement performs the actual firing of the event, specifying the name of the event to fire, and a set of arguments in parentheses.
Public Class Employee Public Name As String Private currentSalary As Decimal Public Property Salary( ) As Decimal Get Return currentSalary End Get Set(ByVal value As Decimal) currentSalary = value RaiseEvent SalaryChanged(currentSalary) End Set End Property Public Event SalaryChanged(ByVal NewSalary As Decimal) End Class
The event handlers are not added directly to the class. Instead, they are attached to an instance of the class. The instance, declared as a class field, must be defined using the special WithEvents
keyword, which tells Visual Basic that this instance will process events.
Public WithEvents MonitoredEmployee As Employee
Event handlers are ordinary subroutines, but they include the Handles
keyword to indicate which event is being handled.
Private Sub EmployeePayChanged( _
ByVal updatedSalary As Decimal) _
Handles
MonitoredEmployee.SalaryChanged
MsgBox("The new salary for " & _
MonitoredEmployee.Name & " is " & updatedSalary)
End Sub
All that is needed is something to kick off the action.
Public Sub HireFred( ) MonitoredEmployee = New Employee MonitoredEmployee.Name = "Fred" MonitoredEmployee.Salary = 50000 ' Triggers event End Sub
When the salary is set, the Employee
class’s Salary
property fires the SalaryChanged
event using the Visual Basic RaiseEvent
command. This generates a call to the EmployeePayChanged
event handler, which finally displays the message.
The events built into the Windows Forms classes in .NET work just like this, but instead of watching with me for a salary increase, they are watching for mouse clicks and keyboard clacks. All of these system events use a common argument signature.
Event EventName
(ByVal sender As System.Object, _
ByVal e As System.EventArgs)
The sender
argument identifies the instance of the object that is firing the event, in case the caller needs to examine its members. The e
argument is an object that lets the caller send event-specific data to the handler through a single class instance. The System.EventArgs
class doesn’t have much in the way of members, but many events use a substitute class that is derived from System.EventArgs
.
As we pass through the chapters of this book, there will be no end to the number of event examples you will see and experience. I will save the more involved and interesting samples until then.
Classes, structures, modules, enumerations, interfaces, and delegates—the major .NET types—don’t just float around in the code of your application. They must all be grouped and managed into namespaces. As described in Chapter 1, namespaces provide a hierarchy for your types, sort of a tree-shaped condominium where each type has a home. Some of those homes (or nodes), such as System
, get pretty crowded with all those type families living there. Others, such as System.Timers
, may have only a few types dwelling in their ample abodes. But every type must live in the hierarchy; none of the types is adventurous enough to strike out on its own and build a ranch house.
At the very root of the hierarchy is Global
, not a node itself, but a Visual Basic keyword that indicates the root of all roots. You can include Global
when referencing your namespaces, but its use is required only when leaving it out would cause confusion between two namespace branches.
Directly under Global
are the few top-level namespaces, including System
and Microsoft
. Each top-level namespace contains subordinate namespaces, and each of those can contain additional third-level namespaces, and so on. Namespace nodes are referenced relative to one another using a “dot” notation.
System.Windows.Forms
This specifies the third-level Forms
namespace. You could also have typed:
Global.System.Windows.Forms
which means the same thing. Relative namespaces are also supported:
Forms
However, to use relative namespaces, you must tell your Visual Basic code to expect them. There are so many namespaces out there, and there may be several Forms
namespaces somewhere in the hierarchy.
Before namespaces can be used in your code, they must be referenced and optionally imported. Referencing a namespace identifies the DLL assembly file that contains that namespace’s types. Perform both of these actions through the References tab of each project’s Properties form (see Figure 2-3).
Actually, you are not referencing the namespaces in the DLL, but rather the types, all of which happen to live in specific namespaces. However, for the core type DLLs supplied with the .NET Framework, it feels like the same thing. In fact, Microsoft even named many of the DLLs to match the namespaces they contain. System.dll contains types within the System
namespace. System.Windows.Forms.dll includes types specific to Windows Forms applications, and all of these types appear in the System.Windows.Forms
namespace or one of its subordinates.
If you don’t reference a DLL in your project, none of its types will be available to you in your code. Visual Studio loads several references into your project automatically based on the type of project you create. Figure 2-3 shows the nine default references included within a Windows Forms application: System
, System.Core
, System.Data
, System.Data.DataSetExtensions
, System.Deployment
, System.Drawing
, System.Windows.Forms
, System.Xml
, and System.Xml.Linq
.
Once you have referenced a library of classes (or other types) in your code, access any of its classes by specifying the full namespace to that class. For instance, the class for an on-screen form is referenced by System.Windows.Forms.Form
. That’s three levels down into the hierarchy, and some classes are even deeper. I hope that your health insurance plan covers carpal tunnel syndrome.
To avoid typing all those long namespaces over and over again, Visual Basic includes an imports feature. Imports are namespace-specific; once a namespace has been imported, you can access any of the types in that namespace without specifying the namespace name. If you import the System.Windows.Forms
namespace, you only have to type “Form” to access the Form
class. The bottom half of Figure 2-3 shows how to set these imports through the project’s properties. The “Imported namespaces” list shows all available referenced namespaces. Simply check the ones you wish to import; System.Windows.Forms
is already checked for you by default in Windows Forms applications.
You can also import a namespace directly in your source code. Use the Imports
statement at the very start of a source code file:
Imports System.Windows.Forms
The Imports
statement supports namespace abbreviations, short names that represent the full namespace in your code. Using the statement:
Imports Fred = System.Windows.Forms
lets you reference the Form
class as “Fred.Form.” Unlike the imports list in the project’s properties, which impacts the entire project, the Imports
statement affects only a single source code file.
By default, all the classes and types in your project appear in a top-level namespace that takes on the name of your project. For a Windows Forms application, this default namespace is called WindowsApplication1
. To specify a different top-level namespace, modify it through the Application tab of the project’s properties, in the “Root namespace” field. All the types in your project appear in this namespace; if you specify an existing Microsoft-supplied namespace as your project’s root namespace, all your types will appear in that specified namespace mixed in with the preexisting types. For standalone applications, this mixture will be visible only from your code.
From the root namespace, you can place types within subordinate namespaces by using the Namespace
statement. Namespace
is a block statement that ends with the End Namespace
clause. Any types you create between the Namespace
and End Namespace
clauses will be contained in that subordinate namespace. For example, if your root namespace is WindowsApplication1
, the following statements create a class whose full name is WindowsApplication1.WorkArea.BasicStuff.BusyData
:
Namespace WorkArea.BasicStuff Class BusyData ... End Class End Namespace
You can include as many Namespace
statements in your code as needed. Nesting of namespaces is also supported:
Namespace WorkArea Namespace BasicStuff Class BusyData ... End Class End Namespace End Namespace
Visual Basic 2005 introduced a new “My” top-level namespace, designed to simplify common programming tasks. Microsoft added it to the language in part to draw holdout Visual Basic 6.0 stalwarts into the .NET fold. But most of it is really not that dramatic. My
collects commonly used features that are currently sprinkled around the Framework Class Library (FCL), and puts them in a mini-hierarchy for convenient access. It’s really not much more complicated than that. The hierarchy is nicely organized, with sections for user, application, and computer-specific information. It’s used just like any other part of the framework, although you cannot use the Imports
keyword to access its components by a relative path.
Overall, My
is very easy to use. To display the version number of your application, for instance, use the following statement:
MsgBox(My.Application.Info.Version.ToString)
Some areas of the My
namespace are dynamic; classes are added or removed as you modify your source code. In Windows Forms applications, the My.Forms
branch includes entries for each one of the project’s forms. As you add new forms, new entries are added automatically. The My.Forms
object then makes a reference to each form available for use in your code.
My.Forms.Form1.Text = "Welcome"
Sadly, this chapter has reached its conclusion. You may feel that it went by all too fast; you may feel that you didn’t really learn how to write Visual Basic programs; you may feel that a mild sedative would be right just about now. But don’t fret. This chapter served as an introduction to the syntax and major features of Visual Basic. Now begins the deeper training. As we start this book’s main focus—the Library Project—you will encounter specific examples of all features covered only briefly in this chapter.
In this chapter, we will use the Code Snippets feature of Visual Studio to insert source code into a basic sample code framework. Code Snippets is essentially a hierarchical database of saved source code text. If you have installed the code for this book, you will find code snippets for most chapters included right in Visual Studio. In this chapter’s project, I will show you how to use them to add chapter-specific code into your project.
Since we haven’t officially started the Library Project, this chapter’s project will simply extend the “Hello, World!” project we developed in Chapter 1, but with fun parts added. I will include some of the language features we discovered throughout this chapter.
Load the Chapter 2 (Before) Code project, either through the New Project templates or by accessing the project directly from the installation directory. To see the code in its final form, load Chapter 2 (After) Code instead.
Each chapter’s sample code includes a “Before” and “After” version. The “After” version represents the code as it will look when all the changes in that chapter’s "Project" section have been applied. The “Before” version doesn’t have any of the chapter’s project changes included, just placeholders where you will insert the code, one block at a time.
Like the project in Chapter 1, this chapter’s project includes a basic Windows form with a single button on it. Clicking on the button displays the same “Hello, World!” message. However, this time, the message starts in an encoded form, and a separate class decodes the message and triggers an event that displays the form.
Once the project is open, view the source code attached to Form1
. It should look somewhat like the following:
Public Class Form1 ' *** Insert Code Snippet #2 here. Private Sub Button1_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles Button1.Click ' *** Insert Code Snippet #3 here. End Sub ' *** Insert Code Snippet #4 here. End Class ' *** Insert Code Snippet #1 here.
This sample uses a separate class to process the displayed message. The code for this class appears as snippet number 1. To insert the snippet, move the cursor just after the #1 snippet marker line, which reads:
' *** Insert Code Snippet #1 here.
To insert a snippet through the Visual Studio menus, select Edit → IntelliSense → Insert Snippet. The equivalent keyboard sequence is Ctrl-K, Ctrl-X. Or type a question mark (?
) anywhere in the source code, followed by pressing the Tab key. Any of these methods displays the first level of snippets (see Figure 2-4).
From the snippet list, select Programming Visual Basic 2008, and then select Chapter 2. A list of the available snippet items for this chapter appears (see Figure 2-5).
Finally, select Item 1. The content magically appears within the source code. All insertions of code snippets throughout this book occur in exactly this way.
Snippet 1 inserts the SayHello
class, part of the HelloStuff
namespace, a portion of which appears here:
Namespace HelloStuff Friend Class SayHello Private secretMessage As String Private reverseFlag As Boolean Private decoded As Boolean Public Event MessageDecoded( _ ByVal decodedMessage As String) Public Sub New(ByVal codedMessage As String, _ ByVal reverseIt As Boolean) ... Public Sub DecodeMessage(ByVal rotationFactor As Integer) ... Public Sub ReportMessage( ) ... End Class End Namespace
The SayHello
class includes three private fields (secretMessage
, reverseFlag
, and decoded
), which monitor the current status of the display message. A constructor (New
) allows the user to create a new instance of SayHello
with an initial message text, and a flag that indicates whether the text should be reversed before display. The DecodeMessage
subroutine converts each letter of the encoded message to its final form by shifting each letter a rotationFactor
number of places. If the letter E appears and rotationFactor
is 3, the letter E is shifted three spaces forward, to H. A negative rotation factor shifts the letters lower in the alphabet. The alphabet wraps at the A-Z boundary. Only letters are rotated, and upper- and lowercase are handled independently.
The ReportMessage
method fires the MessageDecoded
event, sending the previously decoded message to the event as an argument. So, where is this event handler? It’s attached to an instance of SayHello
that will be added to the Form1
class.
Insert Chapter 2, Snippet Item 2.
Private WithEvents HelloDecoder As HelloStuff.SayHello
The HelloDecoder
class is an instance of the HelloStuff.SayHello
class that we just wrote, and the snippet makes it a member of the Form1
class. The WithEvents
keyword says, “This instance will respond to events”; specifically, the MessageDecoded
event from the SayHello
class.
Let’s add the code that triggers the message to display when the user clicks on the on-form button. This occurs in the button’s click event.
Insert Chapter 2, Snippet Item 3.
HelloDecoder = New HelloStuff.SayHello("!iqwtB, tqqjM", True) HelloDecoder.DecodeMessage(-5) HelloDecoder.ReportMessage( )
These three lines create an instance of the SayHello
class, storing it in the HelloDecoder
class field. Can’t read the first argument in the constructor? It’s encoded! It’s a secret! And the True
flag says that it’s been reversed to make it an even bigger secret (you don’t know what it is!). The DecodeMessage
removes the secrets by shifting each letter as needed, although the reversal doesn’t happen until the call to ReportMessage
.
The ReportMessage
method doesn’t actually display the message. Instead, it fires an event that makes the unscrambled message available to an event handler.
Insert Chapter 2, Snippet Item 4.
Private Sub HelloDecoder_MessageDecoded( _ ByVal decodedMessage As String) _ Handles HelloDecoder.MessageDecoded ' ----- Show the decoded message. MsgBox(decodedMessage) End Sub
The Handles
keyword connects the subroutine with the fired event. The decoded message comes into the handler through the decodedMessage
argument, and is splashed all over the screen with a simple yet powerful call to the MsgBox
function.
That’s it for the sample code. Now it’s time to roll up your sleeves and embark on a full Visual Basic 2008 project.
18.224.73.102