Code File Structure


A form, class, or code module should contain the following sections in this order (if they are present):

  • Option statements - Option Explicit, Option Strict, or Option Compare. By default, Option Explicit is on, Option Strict is off, and Option Compare is binary.

  • Imports statements - These declare namespaces that the module will use.

  • A Main subroutine - The routine that starts execution when the program runs.

  • Class, Module, and Namespace statements - As needed.

Important 

To uncover potentially annoying and sometimes elusive bugs, turn Option Explicit on and Option Strict on. Also see the section “Project” in Chapter 1.

Some of these items may be missing. For example, Option and Imports statements are optional. Note that an executable Windows program can start from a Main subroutine or it can start by displaying a form, in which case it doesn’t need a Main subroutine. Classes and code modules don’t need Main subroutines.

The following code shows a simple code module. It sets Option Explicit on (variables must be declared before used) and Option Strict on (implicit type conversions cause an error). It imports the System.IO namespace so the program can easily use classes defined there. It then defines the Employee class.

  Option Explicit On Option Strict On Imports System.IO Public Class Employee     ... End Class 

Usually, you put each class or module in a separate file, but you can add more Class or Module statements to a file if you like. Figure 3-4 shows a code module that contains two classes and two modules.

image from book
Figure 3-4: A single file can contain multiple Class and Module statements.

Class and Module statements define top-level nodes in the code hierarchy. Click the minus sign to the left of one of these statements to collapse the code it contains. When the code is collapsed, click the plus sign to the left of it to expand the code. In Figure 3-4, the AccountingTools and BillingTools modules and the Employee class are collapsed, hiding whatever code they contain.

The project can freely refer to any public class, or to any public variable or routine in a module. If two modules contain a variable or routine with the same name, the program can select the version it wants by prefixing the name with the module’s name. For example, if the AccountingTools and BillingTools modules both have a subroutine named ConnectToDatabase, the following statement executes the version in the BillingTools module:

  BillingTools.ConnectToDatabase 

Code Regions

Class and Module statements define regions of code that you can expand or collapse to make the code easier to understand. Similarly, you can use the Region statement to organize your code. You can place subroutines that have a common purpose in a region so you can collapse and expand the code as needed. The following code shows a simple region:

  #Region "Drawing Routines"     ... #End Region 

Note that the IDE’s search-and-replace features normally work only on expanded regions. If you collapse a region and make a global search-and-replace in the current document or the current selection, the collapsed code remains unchanged. If you make a global replace throughout the whole project, the replacement occurs within collapsed regions as well.

By itself, the End Region statement does not tell you which region it is ending. You can make your code easier to understand, particularly if you have many regions in the same module, by adding a comment after the End Region statement giving the name of the region, as shown in the following code:

  #Region "Drawing Routines"     ... #End Region ' Drawing Routines 

Sometimes it may be easier to move related pieces of code into separate files. The Partial keyword allows you to place parts of a class in different files. For example, you could move a form’s code for loading and saving data in a separate file and use the Partial keyword to indicate that the code was part of the form. Chapter 16 describes the Partial keyword in detail.

However, you cannot use the Partial keyword with modules so a module’s code must all go in one file. In that case, you can use regions to similarly separate a group of related routines and make the code easier to read.

Conditional Compilation

Conditional compilation statements allow you to include or exclude code from the program’s compilation. The basic conditional compilation statement is similar to a multiline If Then Else statement. The following code shows a typical statement. If the value condition1 is True, the code in code_block_1 is included in the compiled program. If that value is False but the value condition2 is True, the code in code_block_2 becomes part of the compiled program. If neither condition is True, the code in code_block_3 is included in the program.

  #If condition1 Then     code_block_1... #ElseIf condition2 Then     code_block_2... #Else     code_block_3... #End If 

It is important to understand that the code not included by the conditional compilation statements is completely omitted from the executable program. At compile time, Visual Studio decides whether a block of code should be included or not. That means any code that is omitted does not take up space in the executable program. It also means that you cannot set the execution statement to omitted lines in the debugger because those lines are not present.

In contrast, a normal If Then Else statement includes all the code in every code block in the executable, and then decides which code to execute at runtime.

Because the conditional compilation statement evaluates its conditions at compile time, those conditions must be expressions that can be evaluated at compile time. For example, they can be expressions containing values that you have defined using compiler directives (described shortly). They cannot include values generated at runtime (such as the value of variables).

In fact, a conditional compilation statement evaluates its conditions at design time, so it can give feedback while you are writing the code. For example, if Option Explicit is set to On, then Visual Basic flags the following assignment statement as in error. Because the first condition is True, the variable X is declared as a string. Option Explicit On disallows implicit conversion from an integer to a string, so the IDE flags the statement as an error.

  #If True Then     Dim X As String #Else     Dim X As Integer #End If     X = 10 

That much makes sense, but it’s also important to realize that the code not included in the compilation is not evaluated by the IDE. If the first condition in the previous code were False, the code would work properly because variable X would be declared as an integer. The IDE doesn’t evaluate the other code, so it doesn’t notice that there is an error if the condition is False. You probably won’t notice the error until you try to actually use the other code.

You can set conditional compilation constants in two main ways: in code and in the project’s compilation settings.

Setting Constants in Code

To set conditional compilation constants explicitly in your program, use a #Const statement, as shown in the following code:

  #Const UserType = "Clerk" #If UserType = "Clerk" Then     ' Do stuff appropriate for clerks...     ... #ElseIf UserType = "Supervisor" Then     ' Do stuff appropriate for supervisors...     ... #Else     ' Do stuff appropriate for others...     ... #End If 

Note that these constants are defined only after the point at which they appear in the code. If you use a constant before it is defined, its value is False (unfortunately Option Explicit doesn’t apply to these constants). That means the following code displays the value Slow followed by the value Fast:

  #If UseFastAlgorithm Then     MessageBox.Show("Fast") #Else     MessageBox.Show("Slow") #End If #Const UseFastAlgorithm = True #If UseFastAlgorithm Then     MessageBox.Show("Fast") #Else     MessageBox.Show("Slow") #End If 

To avoid possible confusion, many programmers define these constants at the beginning of the file.

Also note that your code can redefine a constant using a new #Const statement later. That means these are not really constants in the sense that their values are unchangeable.

Setting Constants with the Project’s Compilation Settings

To set constants with the project’s compilation settings, open Solution Explorer and double-click My Project. Click the Compile tab to see the form shown in Figure 3-5.

image from book
Figure 3-5: Click the Compile tab’s Advanced Compile Options button to set compilation constants.

Click the Advanced Compile Options button to open the Advanced Compiler Settings dialog box shown in Figure 3-6. Enter the names and values of the constants in the Custom Constants text box. Enter each value in the form ConstantName=Value. Separate multiple constants with commas.

image from book
Figure 3-6: Use the Advanced Compiler Settings dialog box to define compilation constants.

Constants you specify on the Advanced Compiler Settings dialog box are available everywhere in the project. However, your code can redefine the constant using a #Const directive. The constant has the new value until the end of the file or until you redefine it again.

Predefined Constants

Visual Basic automatically defines several conditional compilation constants that you can use to determine the code that your application compiles. The following table describes these constants.

Open table as spreadsheet

Constant

Meaning

CONFIG

A string that gives the name of the current build. Typically, this will be “Debug” or “Release”.

DEBUG

A Boolean that indicates whether this is a debug build. By default, this value is True when you build a project’s Debug configuration.

PLATFORM

A string that tells you the target platform for the application’s current configuration. Unless you change this, the value is “AnyCPU”.

TARGET

A string that tells the kind of application the project builds. This can be winexe (Windows form application), exe (console application), library (class library), or module (code module).

TRACE

A Boolean that indicates whether the Trace object should generate output in the Output window.

VBC_VER

A number giving Visual Basic’s major and minor version numbers. The value for Visual Basic 2005 is 8.0.

_MyType

A string that tells what kind of application this is. Typical values are “Console” for a console application, “Windows” for a class or Windows control library, and “WindowsForms” for a Windows forms application.

The following sections describe the DEBUG, TRACE, and CONFIG constants and their normal uses in more detail.

DEBUG

Normally when you make a debug build, Visual Basic sets the DEBUG constant to True. When you compile a release build, Visual Basic sets DEBUG to False. The Configuration Manager lets you select the Debug build, the Release build, or other builds that you defined yourself.

After you have activated the Configuration Manager, you can open it by clicking on the project in the Solution Explorer and then selecting the Build menu’s Configuration Manager command. Figure 3-7 shows the Configuration Manager. Select Debug or Release from the drop-down list, and click Close.

image from book
Figure 3-7: Use the Configuration Manager to select a Debug or Release build.

Tip 

If the Configuration Manager is not available in the Build menu, open the Tools menu and select the Options command. Open the Projects and Solutions node’s General entry, and select the Show advanced build configurations check box.

When the DEBUG constant is True, the Debug object’s methods send output to the Output window. When the DEBUG constant is not True, the Debug object’s methods do not generate any code, so the object doesn’t produce any output. This makes the Debug object useful for displaying diagnostic messages during development and then hiding the messages in release builds sent to customers.

The following sections describe some of the Debug object’s most useful properties and methods.

Assert

The Debug.Assert method evaluates a Boolean expression and, if the expression is False, displays an error message. This method can optionally take as parameters an error message and a detailed message to display. The following code shows how a program might use Debug.Assert to verify that the variable NumEmployees is greater than zero:

  Debug.Assert(NumEmployees > 0, _     "The number of employees must be greater than zero.", _     "The program cannot generate timesheets if no employees are defined") 

Figure 3-8 shows the dialog box displayed by this statement if NumEmployees is zero.

image from book
Figure 3-8: Debug.Assert displays an error message if a Boolean expression is False.

If you click the dialog box’s Abort button, the program immediately halts. If you click Retry, the program breaks into the debugger, so you can examine the code. If you click Ignore, the program continues as if the Assert statement’s condition was True.

A good use for the Assert method is to verify that a routine’s parameters or other variable values are reasonable before starting calculations. For example, suppose that the AssignJob subroutine assigns a repair person to a job. The routine could begin with a series of Assert statements that verify that the person exists, the job exists, the person has the skills necessary to perform the job, and so forth. It is usually easier to fix code if you catch these sorts of errors before starting a long calculation or database modification that may later fail because, for example, the repair person doesn’t have the right kind of truck to perform the job.

If the DEBUG constant is not True, the Assert method does nothing. This lets you automatically remove these rather obscure error messages from the compiled executable that you send to customers. You wouldn’t want a user to see the dialog box in Figure 3-8!

You should take some care when deciding what tests should be placed in Assert statements so that they are removed from the compiled executable. For example, you might use Assert to verify that a string entered by the user contains a valid value. When you run the compile executable, however, this test is removed, so the program does not protect itself from bad data. When you use Assert to verify a condition, you must be certain that the program can run safely if the Assert statement is removed and the condition fails.

Fail

The Debug.Fail method displays an error message just as Debug.Assert does when its Boolean condition parameter is False.

IndentSize, Indent, Unindent, and IndentLevel

These properties and methods determine the amount of indentation used when the Debug object writes into the Output window. You can use them, for example, to indent the output in subroutines to show the program’s structure more clearly.

The IndentSize property indicates the number of spaces that should be used for each level of indentation. The IndentLevel property determines the current indentation level. For example, if IndentSize is 4 and IndentLevel is 2, output is indented by eight spaces.

The Indent and Unindent methods increase and decrease the indentation level by one.

Write, WriteLine, WriteIf, and WriteLineIf

These routines send output to the Output window. The Write method prints text and stops without starting a new line. WriteLine prints text and follows it with a new line.

The WriteIf and WriteLineIf methods take a Boolean parameter and act the same as Write and WriteLine if the parameter’s value is True.

TRACE

The Trace object is very similar to the Debug object and provides the same set of properties and methods. The difference is that it generates output when the TRACE constant is defined rather than when the DEBUG constant is defined.

Normally, the TRACE constant is defined for both debug and release builds so Trace.Assert and other Trace object methods work in both builds. By default, DEBUG is defined only for debug builds, so you get Debug messages for debug builds. You can add listener objects to the Trace object (or the Debug object) to perform different actions on any Trace output. For example, a listener could write the Trace output into a log file.

CONFIG

The Config constant’s value is the name of the type of build. Normally, this is either Debug or Release, but you can also create your own build configurations. You can use these for interim builds, point releases, alpha and beta releases, or any other release category you can think of.

To select the debug or release build, click on the project in the Solution Explorer and then select the Build menu’s Configuration Manager command to display the dialog box shown in Figure 3-7. Select <New...> from the dropdown list to display the New Project Configuration dialog box shown in Figure 3-9. Enter a name for the new configuration, select the existing configuration from which the new one should initially copy its settings, and click OK.

image from book
Figure 3-9: You can use the Configuration Manager to create your own configurations.

The following code shows how to use the CONFIG compiler constant to determine which build is being made and take different actions accordingly:

  #If CONFIG = "Debug" Then     ' Do stuff for a Debug build... #ElseIf CONFIG = "Release" Then     ' Do stuff for a Release build... #ElseIf CONFIG = "InterimBuild" Then     ' Do stuff for a custom InterimBuild... #Else     MsgBox("Unknown build type") #End If 

Note that a compilation constant’s value is case-sensitive; for example, you should compare CONFIG to “Debug” not “debug” or “DEBUG.”

One reason you might want to make different configurations is to handle variations among operating systems. Your code can decide which configuration is in effect, and then execute the appropriate code to handle the target operating system. For example, it might need to work around the reduced privileges that are granted by default on Vista.

Debugging Level Constants

Sometimes it is helpful to be able to easily adjust the level of diagnostic output a program generates. You could define the constant DEBUG_LEVEL and then send data to the Output window, depending on its value. For example, you might place level 1 Debug statements in major subroutines, level 2 statements in secondary routines, and level 3 statements throughout important routines to provide step-by-step information. Then you can define the DEBUG_LEVEL constant to quickly give you the amount of information you want.

The following code shows a small example. The IsPhoneNumberValid function determines whether its parameter looks like a valid 7- or 10-digit U.S. phone number. If DEBUG_LEVEL is at least 1, the function displays messages when it starts and when it exits. It also indents the output when it starts and unindents the output before it exits. If DEBUG_LEVEL is at least 2, the function also displays statements telling when it is about to check for 7- and 10-digit phone numbers.

  #Const DEBUG_LEVEL = 2     Private Function IsPhoneNumberValid(ByVal phone_number As String) As Boolean #If DEBUG_LEVEL >= 1 Then         Debug.WriteLine("Entering IsPhoneNumberValid(" & phone_number & ")")         Debug.Indent() #End If         ' Check for a 7-digit phone number. #If DEBUG_LEVEL >= 2 Then         Debug.WriteLine("Checking for 7-digit phone number") #End If         Dim is_valid As Boolean = _             phone_number Like "-#"         If Not is_valid Then #If DEBUG_LEVEL >= 2 Then             Debug.WriteLine("Checking for 10-digit phone number") #End If             is_valid = phone_number Like "--#"         End If #If DEBUG_LEVEL >= 1 Then         Debug.Unindent()         Debug.WriteLine("Leaving IsPhoneNumberValid, returning " & is_valid) #End If         Return is_valid     End Function 

The following sample shows the results in the Output window when DEBUG_LEVEL is set to 2:

  Entering IsPhoneNumberValid(123-4567)     Checking for 7-digit phone number Leaving IsPhoneNumberValid, returning True 

From this output, you can tell that the function examined the string 123-4567, did not need to check for a 10-digit phone number, and returned True.

For more information on debugging Visual Basic applications, see Chapter 8.

Namespaces

Visual Studio uses namespaces to categorize code. A namespace can contain other namespaces, which can contain others, forming a hierarchy of namespaces.

You can define your own namespaces to help categorize your code. By placing different routines in separate namespaces, you can allow pieces of code to include only the namespaces they are actually using. That makes it easier to ignore the routines that the program isn’t using. It also allows more than one namespace to define items that have the same names.

For example, you could define an Accounting namespace that contains the AccountsReceivable and AccountsPayable namespaces. Each of those might contain a subroutine named ListOutstandingInvoices. The program could select one version or the other by calling either Accounting.Accounts?Receivable.ListOutstandingInvoices or Accounting.AccountsPayable.ListOut?standingInvoices.

You can only use the Namespace statement at the file level or inside another namespace, not within a class or module. Within a namespace, you can define nested namespaces, classes, or modules. The following example defines the AccountingModules namespace. That namespace contains the two classes PayableItem and ReceivableItem, the module AccountingRoutines, and the nested namespace OrderEntryModules. The AccountingRoutines module defines the PayInvoice subroutine. All the classes, modules, and namespaces may define other items.

  Namespace AccountingModules     Public Class PayableItem         ...     End Class     Public Class ReceivableItem         ...     End Class     Module AccountingRoutines         Public Sub PayInvoice(ByVal invoice_number As Long)             ...         End Sub             ...     End Module     Namespace OrderEntryModules         Public Class OrderEntryClerk             ...         End Class         ...     End Namespace End Namespace 

Code using a module’s namespace does not need to explicitly identify the module. If a module defines a variable or routine that has a unique name, you do not need to specify the module’s name to use that item. In this example, there is only one subroutine named PayInvoice, so the code can invoke it as AccountingModules.PayInvoice(). If the AccountingModules namespace contained another module that defined a PayInvoice subroutine, the code would need to indicate which version to use as in AccountingModules.AccountingRoutines.PayInvoice().

Although modules are transparent within their namespaces, nested namespaces are not. Because the nested OrderEntryModules namespace defines the OrderEntryClerk class, the code must specify the full namespace path to the class, as in the following code:

  Dim oe_clerk As New AccountingModules.OrderEntryModules.OrderEntryClerk 

Note that a Visual Basic project defines its own namespace that contains everything else in the project. Normally, the namespace has the same name as the project. To view or modify this root namespace, double-click on the Solution Explorer’s My Project entry to open the project property page shown in Figure 3-10. Enter the new root namespace name in the text box in the upper right.

image from book
Figure 3-10: The project’s Application property page lets you view or change the project’s root namespace.

You can use an Imports statement to simplify access to a namespace inside a file. For example, suppose that you are working on the GeneralAccounting project that has the root namespace GeneralAccounting. The first statement in the following code allows the program to use items defined in the AccountingModules namespace without prefixing them with AccountingModules. The second statement lets the program use items defined in the AccountingModules nested namespace OrderEntryModules. The last two lines of code declare variables using classes defined in those namespaces.

  Imports GeneralAccounting.AccountingModules Imports GeneralAccounting.AccountingModules.OrderEntryModules ... Private m_OverdueItem As PayableItem    ' In the AccountingModules namespace. Private m_ThisClerk As OrderEntryClerk  ' In the namespace                                         ' AccountingModules.OrderEntryModules. 




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

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