CODE FILE STRUCTURE

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

  • Option statements — Option Explicit, Option Strict, Option Compare, or Option Infer. By default, Option Explicit is on, Option Strict is off, Option Compare is binary, and Option Infer is on.
  • 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.

DEBUGGING OPTIONS
To uncover potentially annoying and sometimes elusive bugs, turn Option Explicit on, Option Strict on, and Option Infer off. The section “Project” in Chapter 2 describes these options.

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. (In that case, the program starts with the automatically generated New subroutine in the file Application.Designer.vb.) Classes and code modules don’t need Main subroutines.

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

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

Usually, you put each class or module in a separate file, but you can add multiple Class or Module statements to the same file if you like.

Class and Module statements define top-level nodes in the code hierarchy. Click the minus sign to the left of one of these statements in the code editor to collapse the code it contains. When the code is collapsed, click the plus sign to the left of it to expand the code.

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. Subroutines and functions also define collapsible code sections. In addition to these, you can use the Region statement to create your own collapsible sections of 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

RENAME, DON’T REPLACE
Instead of using a global find and replace to rename a variable, class, or other programming entity, use Visual Basic’s renaming feature. Right-click the entity you want to rename and select Rename. Enter the new name and click OK. Visual Basic will change all occurrences of the entity in every module as needed.
Using rename instead of global replace makes it easier to rename one variable while not renaming other variables with the same name in different scopes. It also prevents annoying replacement errors. For example, if you use global replace to change “man” to “person,” you may accidentally change “manager” to “personager” and “command” to “compersond.”

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

REAL-LIFE REGIONS
I use regions a lot in my code. They make it easy to collapse code that I’m not working on and they group related code into meaningful sections. Just building the regions helps me put related material together and makes reading the code easier.

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 into a separate file and use the Partial keyword to indicate that the code was part of the form. Chapter 23, “Classes and Structures,” 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 or not a block of code should be included. 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 program, and then decides which code to execute at run time.

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 run time (such as the values of variables).

In fact, a conditional compilation statement actually evaluates its conditions at design time, not compile time, so it can give feedback while you are writing the code. For example, suppose Option Explicit is set to On. 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 so the IDE doesn’t notice that they are undefined at that point.)

To avoid possible confusion, many programmers define these constants at the beginning of the file so they don’t need to worry about using a variable before it is defined.

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. Select the Compile tab and click its Advanced Compile Options button to open the Advanced Compiler Settings dialog box shown in Figure 13-4. Enter the names and values of the constants in the Custom Constants text box. Enter each value in the form ConstantName=Value, separating multiple constants with commas.

FIGURE 13-4: Use the Advanced Compiler Settings dialog box to define compilation constants.

image

Constants that 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.

Example program CompilerConstantsSettings, which is available for download on the book’s website, includes constants set on this dialog box and code to check their values.

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.


CONSTANT CASE
Compilation constant values are case-sensitive. For example, you should compare CONFIG to “Debug” not “debug” or “DEBUG.”
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 or WPF 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 and the value for Visual Basic 2008 is 9.0. The value for Visual Basic 2010 should logically be 10.0 but it was not updated so it remained 9.0. The value for Visual Basic 2012 is 11.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.


MORE ON _MYTYPE
For more information on _MyType and how it relates to other special compilation constants, see http://msdn2.microsoft.com/ms233781.aspx.

Example program CompilerConstantsInCode, which is available for download on the book’s website, shows how a program can check these compiler constants. Example program WpfCompilerConstantsInCode, which is also available for download, is a WPF version of the same program.

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 define yourself.

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

FIGURE 13-5: Use the Configuration Manager to select a Debug or Release build.

image

THE MISSING MANAGER MYSTERY
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")

Example program EmployeeAssert, which is available for download on the book’s website, demonstrates this Debug.Assert statement.

If NumEmployees is less than or equal to zero, this statement displays an error dialog box that shows the error message and the detailed message. It also displays a long stack dump that shows exactly what code called what other code to reach this point of execution. Only the first few entries will make sense to practically anyone because the stack dump quickly moves out of the application’s code and into the supporting Visual Basic libraries that execute the program.

The dialog box also displays three buttons labeled Abort, Retry, and Ignore. If you click the 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 repairperson 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 repairperson 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. The dialog box with its messages and stack dump is so technical that it would terrify many users anyway, so there’s no point inflicting it on them.

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 to indent the output produced by 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 create a new build type, click the project in the Solution Explorer and then select the Build menu’s Configuration Manager command to display the dialog box shown in Figure 13-5. Open the Active Solution Configuration drop-down and select <New. . . > to display the New Project Configuration dialog box. Enter a name for the new configuration, select the existing configuration from which the new one should initially copy its settings, and click OK.

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
    MessageBox.Show("Unknown build type")
#End if

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.

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.AccountsReceivable.ListOutstandingInvoices or Accounting.AccountsPayable.ListOutstandingInvoices.

You can use the Namespace statement only 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

NORMAL NAMESPACES
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 the Solution Explorer’s My Project entry to open the project’s property pages and select the Application tab. Enter the new root namespace name in the text box labeled Root Namespace in the upper right.

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.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.168.152