Moving Beyond Visual Basic 6.0

I l @ ve RuBoard

Visual Basic .NET provides a number of new language-specific features. I'll focus here on three of them: the Option Strict compilation option, operator short-circuiting , and the Declare statement. All of these features are either unique to Visual Basic .NET or are implemented in a very language-specific way.

Option Strict Is Not Optional

Visual Basic developers are all familiar with the Option Explicit compilation option and know how important it is as a development practice. Without the Option Explicit directive, Visual Basic won't require using the Dim statement to declare a variable ”it will implicitly create variables for you as they are referenced. This will cause problems if you misspell a variable. If you specify Option Explicit , Visual Basic will generate a compiler error. If you don't use Option Explicit , Visual Basic will create a new variable each time you misspell an existing one. Worse , Visual Basic will always create a variable of type Variant (if you use Visual Basic 6.0) and type System.Object (if you use Visual Basic .NET). Not only will your variables be declared for you, but you'll lose control over the variable's type. This makes bugs terribly hard to track down. Making the situation worse, in Visual Basic 6.0 Option Explicit is not the default ”you have to be diligent to ensure that each of your class and module files has the Option Explicit declaration at the top. Thankfully, in Visual Basic .NET Option Explicit is on by default.

Visual Basic .NET also goes further and adds a related compilation directive: Option Strict . Option Strict On imposes four essential restrictions on your code:

  • You must use a cast operator when you cause a narrowing conversion.

  • Late binding is forbidden.

  • Any operations on type Object other than = , <> , TypeOf...Is , and Is are not permitted.

  • You must explicitly specify the variable type in all variable declarations by using the As clause.

Of these four requirements, two require a little more investigation: the restrictions on narrowing conversions and the prohibition against late binding.

Narrowing Conversions

Type conversion is the process of converting one type into another. The simplest example of a type conversion is storing a String object as a variable of type Object . This involves casting to a type further up its inheritance hierarchy and is referred to as a widening conversion . (All classes have System.Object as a base class.) A narrowing conversion, on the other hand, is a special type of conversion that comprises one or more of the following:

  • A type conversion that cannot be proven to always succeed at compile time

  • A conversion that is known to possibly lose information (for example, converting from a Double to an Integer )

  • A conversion across types that are sufficiently different from each other to necessitate using explicit narrowing notation

The following conversions are among those classified as narrowing conversions by the MSDN documentation. The list is excerpted from section 11.4 of the Visual Basic Language Specification.

  • Conversions from any type to a more derived type (down the inheritance hierarchy to derived types instead of base types).

  • Numeric type conversions in the following direction: Double --> Single --> Decimal --> Long --> Integer --> Short --> Byte . Each conversion results in an implicit loss of data.

  • Conversions from Boolean to any numeric type. The literal True converts to the literal 255 for Byte and to the expression -1 for Short, Integer, Long, Decimal, Single , and Double . The literal False converts to the literal 0.

  • Conversions from any numeric type to Boolean . A 0 value converts to the literal False . Any other value converts to the literal True .

  • Conversions from any numeric type to any enumerated type.

    More Info

    Enumerated types are explained in more detail in the section titled "Types in Visual Basic .NET" later in this chapter.


  • Conversions from any enumerated type to any other enumerated type.

  • Conversions from any enumerated type to any type its underlying type has a narrowing conversion to.

  • Conversions from any enumerated type to any other enumerated type. A narrowing enumeration conversion between two types is processed by treating any participating enumerated type as the underlying type of that enumerated type, and then performing a widening or narrowing numeric conversion between the resulting types.

  • Conversions from any class type to any interface type, provided that the class type does not implement the interface type.

  • Conversions from any interface type to any class type.

  • Conversions from any interface type to any other interface type, provided that there is no inheritance relationship between the two types.

  • Conversions from an array type S with an element type SE to a covariant -array type T with an element type TE , provided that all of the following are true: S and T differ only in element type, both SE and TE are reference types, and a narrowing reference conversion exists from SE to TE .

  • Conversions from String to Char .

  • Conversions from String to Char() .

  • Conversions from String to Boolean and from Boolean to String . The literal True converts to and from the string " True" ; the literal False converts to and from the string " False".

  • Conversions between String and Byte , Short , Integer , Long , Decimal , Single , or Double . The numeric types are converted to and from strings at run time using the current runtime culture information.

  • Conversions from String to Date and from Date to String . Date values are converted to and from strings at run time using the current runtime culture information.

  • Conversions from any interface type to any value type that implements the interface type.

Even a brief reading of this list should give you a sense that the conversions are dangerous from the standpoint of logical correctness. It's clearly important to ensure that if the conversion is intended, it is stated explicitly. Other ­wise, with Option Strict set to Off , you might perform a narrowing conversion without realizing it, which can result in unexpected application behavior. These kinds of logic errors are often difficult to track down.

This is not to say that narrowing conversions are never appropriate. Option Strict just requires that you explicitly specify the cast (making the code very easy for the casual reviewer to understand). It is a way of forcing developers to explicitly state their intentions rather than assuming that others will know that a narrowing conversion was intended.

Late Binding

As mentioned earlier, Option Strict disallows something called late binding. For anyone unfamiliar with the concept of late binding, I'll provide a little background. Binding is the process of evaluating an object's type information. Early binding means that an object's type information is evaluated and processed at compile time. Early binding typically requires the use of strongly typed variables ”variables whose type is defined explicitly. It also requires the use of a method or property that is explicitly defined by an object's type. This gives the compiler the required information to resolve the type's method and property signatures at compile time, resulting in simple method invocations at run time because that object's signature (interfaces, methods , parameters, and properties) are known.

Late binding delays the resolving of the object's type information until run time. Essentially, it provides a way to access objects in a typeless manner using the System.Object type. (The real type is uncertain at compile time.) Thus, method invocations are handled on a method-by-method basis and Visual Basic inserts code to dynamically look up an object's methods and properties at run time.

Note

Late binding allows a single variable to contain a variety of unrelated types. This can give you extraordinary design flexibility but often leads to subtle programmatic errors. In Visual Basic 6.0, late binding was implemented using COM Automation (implementing the IDispatch interface). Late binding in Visual Basic .NET is accomplished through a mechanism called reflection. Despite the two different implementation mechanisms, the end result is the same: runtime method signature lookups. Visual Basic .NET allows you to design much better solutions by using class and interface inheritance and thereby avoiding late binding altogether.


Even though late binding provides a great deal of flexibility, it has two major drawbacks: bugs and performance issues. Consider the following code example:

 SubMain() Dimo=NewSystem.Random() Console.WriteLine(o.Value) EndSub 

This code compiles fine but it will throw a System.MissingMethodException at run time because the Random class does not contain a public method called Value . If Option Strict is set to On , the compiler will not allow you to specify methods that don't exist, which forces you to fix the problem then and there. The compiler will do this in two ways. First, the background compiler will insert a "squiggly" line underneath the errant method along with a tooltip error message that corresponds to an entry in the Visual Basic .NET task list. Second, if you attempt to build the component, the build operation will fail and the errant method will be listed in the task list.

Option Strict ultimately gives you the choice between generating a compile-time error or a runtime failure. Allowing late binding prevents the detection of possible code failures that might otherwise be caught at compile time. Having to locate and fix the bug at some later point ( especially if your product has already shipped) can be significantly more costly than catching it up front at compile time. In addition to these problems, using late binding means you lose many productivity-enhancing features of the Visual Basic IDE. You won't get IntelliSense assistance on your late-bound objects.

More Info

I'll explore performance issues related to late binding in Chapter 11, "Common Performance Issues."


As mentioned earlier, even if the method does exist on the object, the compiler must generate more code to look up the method and execute it. Turning on Option Strict will prevent this. For example, in the previous code snippet we set Option Strict to On , and therefore we must declare all variables explicitly. In this case, the variable o must be declared as type Random so that we can access its properties. When we do so, we discover that we want to use the Next property, not the Value property. If you were to declare o as type Object (and set Option Strict to Off ), the Visual Basic .NET compiler would have to insert code to look up the property Next at run time.

The following code makes a call to the Next property without all of the extra behind-the-scenes method calls and gives you the benefit of additional compile-time validation:

 OptionStrictOn SubMain() DimoAsNewSystem.Random() Console.WriteLine(o.Next) EndSub 

The important thing to take away from this discussion is that the compiler can generate more optimized (faster and less resource- intensive ) code if Option Strict is set to On . It also enables you to catch simple programming problems at compile time that otherwise might slip through and be discovered by users, resulting in a bad user experience. Nothing says confidence like an application that crashes!

I encourage you to play with Option Strict in your projects just to get a feel for it. The importance of using this feature cannot be overstated. In small development projects, developers can get away with programming idiosyncrasies. Larger development projects require a higher level of quality and standards for everyone involved, otherwise the process itself will spiral out of control. So use Option Strict On and do not consider it optional for any of your projects.

Short-Circuiting Your Operators

We're all familiar with the logical operators And and Or . They perform simple but necessary logical evaluations. The behavior of these operators is not always obvious and can lead to some confusion, especially if you've used other languages such as C++ or C#. To see what I'm talking about, look at the following code sample:

 SubMain() 'Case1: IfFunctionTrue()OrFunctionFalse()Then 'DoSomething EndIf 'Case2: IfFunctionFalse()AndFunctionTrue()Then 'DoSomethingelse EndIf EndSub FunctionFunctionTrue()AsBoolean 'Hugecomputationlogic ReturnTrue EndFunction FunctionFunctionFalse()AsBoolean 'LotsofDatabaseactivity ReturnFalse EndFunction 

In Sub Main , both cases result in FunctionTrue and FunctionFalse being called, regardless of the return values. In this respect, the And and Or logical operators always evaluate their arguments ”without exception. With me so far?

Table 2-1 and Table 2-2 describe how these operators work. But notice that with both operators, the output is highly predictable. In the case of the And operator, if the first argument is False , the result is always False . In the case of the Or operator, if the first argument is True , the result is always True . What does this mean? It means that there's a way to optimize the And and Or operators: conditional short-circuiting.

Table 2-1. Truth Table for the And Operator (Result = A And B)

A

B

Result

False

False

False

False

True

False

True

False

False

True

True

True

Table 2-2. Truth Table for the Or Operator (Result = A Or B)

A

B

Result

False

False

False

False

True

True

True

False

True

True

True

True

Visual Basic .NET introduces the AndAlso and OrElse operators to allow conditional short-circuiting. Both of these operators determine whether to evaluate the second argument depending on the value of the first argument. (Remember that And and Or always evaluate both arguments.) Table 2-3 and Table 2-4 show the result when the second argument is evaluated. The AndAlso operator looks at the first argument of the expression A . If it is False , AndAlso doesn't bother to evaluate B . (The X in the table means "don't care" because the expression cannot be True .) On the other hand, if A is True , AndAlso must evaluate B to determine the result.

Table 2-3. Truth Table for the AndAlso Operator (Result = A AndAlso B)

A

B

Result

False

X

False

True

False

False

True

True

True

Table 2-4. Truth Table for the OrElse Operator (Result = A OrElse B)

A

B

Result

False

False

False

False

True

True

True

X

True

Using the previous code example, replacing Or with OrElse and And with AndAlso in the Sub Main will eliminate the need to run FunctionFalse in the first case and FunctionTrue in the second case. This can increase the efficiency of your application's logical expressions, but it can also lead to confusion. If you decide to use the short-circuit conditional operators, be sure that you aren't relying on some of the arguments for critical code (such as initialization routines), especially if the argument might not get evaluated. On the other hand, short-circuited operators allow more compact code. Take a look at the following example and notice how the AndAlso operator allows the evaluation logic to be performed with a single If block:

 DimcustAsCustomer ... 'Short-circuitingallowsanicecompactstatement If(NotcustIsNothing)AndAlsocust.IsValidThen ... EndIf 'Ifwetriedthis,wewouldgeta 'NullReferenceExceptionifcust=Nothing If(NotcustIsNothing)Andcust.IsValidThen ... EndIf 'Thestandardoperatorsrequirealittlemore 'verbosecodetogetitright IfNotcustIsNothingThen Ifcust.IsValidThen ... EndIf EndIf 

Blindly converting operators in existing projects can introduce logical errors. When you're converting operators in existing projects, do so carefully and thoughtfully. If you're starting a new project using Visual Basic .NET, there is absolutely no reason why these operators shouldn't be in your repertoire . They can contribute to both execution and coding efficiency as well as code readability. Rock on.

Note

In early beta versions of Visual Basic .NET, the And and Or operators were short-circuited by default, but this was changed for backward compatibility reasons and the AndAlso and OrElse operators were introduced. I'm not exactly thrilled by this ”I prefer the earlier behavior ”but the decision was made for a reason and highlights beautifully the reality that software development (including development languages) is an exercise in balancing competing interests.


Calling Platform Functions Directly: Declare and DllImport

The .NET Framework class library is quite extensive . It provides a great deal of functionality ”for creating Windows Forms and Web applications, data access, data structures, Windows services, and so on. For the most part, the .NET Framework provides for all of your development needs. But in some situations you'll need to call Win32 functions directly. (A custom DLL is a good example.) Visual Basic .NET continues to support and enhance the Declare statement for just this purpose. Declare statements are valid only in classes and modules. We'll briefly discuss this topic here and cover it in much more depth in Chapter 4.

Warning

Be careful when you decide to use a Win32 API call instead of the .NET Framework. Sometimes it can be difficult to find certain features in the .NET Framework (especially when you're first familiarizing yourself with the framework), and it might take a good deal of searching to find them. A lot of work has gone into making the framework classes very robust. If you choose to forgo using the .NET Framework, you'll have to do more coding to achieve the level of error checking and handling that the framework classes provide. If the functionality just isn't exposed, fine. Otherwise, you probably shouldn't bother trying to reproduce what is already available in the .NET Framework. The .NET Framework significantly reduces the need to use Win32 APIs, so you should question their use each and every time.


One possible reason for using Declare is to support INI configuration file access. The .NET Framework does not provide direct support for INI files ”XML configuration files are the preferred way to store configuration information. However, some businesses still rely on INI files. For example, older applications that use INI files might need to share them with newer versions. In this case, you need to be able to read INI files from your application. This functionality is not provided by the .NET Framework, so you have to call the GetPrivateProfileString Win32 API method directly from the Kernel32 library. The following code shows one way to do this:

 PublicDeclareAnsiFunctionGetPrivateProfileStringLib "kernel32" Alias_  "GetPrivateProfileStringA" (ByValapplicationNameAsString,_ ByValkeyNameAsString,_ ByValdefaultValueAsString,_ ByValreturnedStringAsSystem.Text.StringBuilder,_ ByValsizeAsInteger,_ ByValfileNameAsString)AsInteger SubMain() DimsbAsNewSystem.Text.StringBuilder(256) DimxAsInteger=GetPrivateProfileString("Mail",_  "MAPI",_  "Default",_ sb,_ sb.Capacity,_  "C:\WINDOWS\win.ini") Console.WriteLine(sb.ToString) EndSub 

Visual Basic .NET provides an alternative way to import native methods without using the Declare statement: the DllImport attribute. The following code is similar in functionality to the previous example:

 ImportsSystem.Runtime.InteropServices PublicModuleModule1 SubMain() DimsbAsNewSystem.Text.StringBuilder(256) DimxAsInteger=GetPrivateProfileString("Mail",_  "MAPI",_  "Default",_ sb,_ sb.Capacity,_  "C:\WINDOWS\win.ini") Console.WriteLine(sb) EndSub <DllImport("KERNEL32.DLL",EntryPoint:="GetPrivateProfileStringA",_ SetLastError:=True,CharSet:=CharSet.Ansi,ExactSpelling:=True,_ CallingConvention:=CallingConvention.WinApi)>_ PublicFunctionGetPrivateProfileString(_ ByValapplicationNameAsString,_ ByValkeyNameAsString,_ ByValdefaultValueAsString,_ ByValreturnedStringAsSystem.Text.StringBuilder,_ ByValsizeAsInteger,_ ByValfileNameAsString)AsInteger EndFunction EndModule 

The DllImport attribute forwards all calls to the GetPrivateProfileString method to the GetPrivateProfileStringA function in the Kernel32 library. Note that these two native method invocation forms are functionally distinct. The DllImport attribute is a syntax that is common across languages; the Declare statement is specific to Visual Basic .NET. In addition, the Declare statement often injects more parameter attributes to ensure that the Declare statement mirrors the Visual Basic 6.0 Declare statement behavior as closely as possible. This means that the Declare statement is often not the most performance-inducing option. It also lacks the flexibility of DllImport ” it gives you fewer options and less control over the method importing process.

So when should you use Declare ? When performance is not an issue and the default Declare behavior is suited to your needs, there is no reason to choose DllImport . You should absolutely use DllImport when performance is a consideration or the target method has requirements that cannot be addressed using the Declare statement (such as nonstandard calling conventions or tricky parameter marshaling). Generally speaking, for the enterprise developer I wholeheartedly endorse using DllImport consistently. This allows you to ignore any performance considerations and ensures a reliable standard throughout your project regardless of the development language.

I l @ ve RuBoard


Designing Enterprise Applications with Microsoft Visual Basic .NET
Designing Enterprise Applications with Microsoft Visual Basic .NET (Pro-Developer)
ISBN: 073561721X
EAN: 2147483647
Year: 2002
Pages: 103

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