When an interface call is made on an object (such as calling a method or setting a property), a number of things happen. Even though you reference a member's
As a Visual Basic .NET developer, you'll probably never actually use a member ID to invoke a member of an object—you'll always reference a member's name instead. The calls you make to a member using its name must be cross-referenced to the proper ID, however, before the member can be invoked. This process (known as binding ) has two forms: early binding and late binding .
An object can contain any number of properties,
When you dimension a variable as type Object, as shown in the following code, you are using late binding.
Dim obj As Object obj = New MyObject()
When you late-bind an object, the binding occurs at run time when the New keyword is used to instantiate an object. When you reference an interface member in code, such as by calling a method or setting a property, Visual Basic .NET needs to obtain the ID of the member in order to successfully invoke it. This involves querying the object for the interface member and its associated ID, preparing any arguments that need to be supplied to the member, and finally invoking the member using the ID and passing associated arguments. These steps require a great deal of overhead and therefore adversely affect the performance of the application.
Late binding is not the preferred method of binding. There are, however, some occasions where late binding is desirable. You might want to use late binding when
You want to write code that can work with many different types of objects.
A type library is not available for an object.
You won't be able to use Option Strict in a project that uses late binding. Using Option Strict promotes more reliable code, and you should consider this when deciding whether or not to use late binding.
If Visual Basic can determine the internal ID for a member at compile time, there is no need to query the object for its interface members and IDs at run time. Instead, the ID can be saved within the compiled application, resulting in considerably faster calls to object members. When an object variable is declared so that the IDs of the object can be resolved at design time, the object variable is said to be early bound .
Early binding occurs at compile time; late binding occurs at run time.
The following are important reasons to use early binding:
Objects, their properties, and their methods appear in the IntelliSense drop-down lists (the lists that appear when you type a period after typing an object's name).
Increased code stability due to the fact that the compiler can check for syntax and reference errors in your code. Visual Basic .NET is able to validate that a given member exists and that you have supplied the proper type, count, and order of parameters at design time.
Dynamic help in the integrated development environment (IDE) is available when working with the object because, once again, Visual Basic .NET is able to identify and understand the object's structure at design time.
For early binding to take place, an object variable must be declared as a specific object type (that is, not using As Object ).
Private Sub DrawText( ByVal TextToDraw As String ) Dim objGraphics As Object Dim objFont As Object ' Create a graphics object using the memory bitmap. objGraphics = Graphics.FromImage(m_objDrawingSurface) ' Create a new font object. objFont = New System.Drawing.Font("Arial", _ mc_FontSize, FontStyle.Bold) ' Draw the user's text. objGraphics.DrawString(TextToDraw, objFont, _ System.Drawing.Brushes.Red, _ mc_TextX, mc_TextY) ' Clean up. objGraphics.Dispose() ' Force the form to paint itself. This triggers the Paint event. Me .Invalidate() End Sub
Private Sub DrawText( ByVal TextToDraw As String ) Dim objGraphics As Graphics Dim objFont As Font ' Create a graphics object using the memory bitmap. objGraphics = Graphics.FromImage(m_objDrawingSurface) ' Create a new font object. objFont = New System.Drawing.Font("Arial", _ mc_FontSize, FontStyle.Bold) ' Draw the user's text. objGraphics.DrawString(TextToDraw, objFont, _ System.Drawing.Brushes.Red, _ mc_TextX, mc_TextY) ' Clean up. objGraphics.Dispose() ' Force the form to paint itself. This triggers the Paint event. Me .Invalidate() End Sub
In previous editions of Visual Basic, you often had to
Windows APIs are unmanaged code; they use different data types than do .NET applications, and they support no type libraries—they're not even COM objects. One result of this fact is that Windows APIs don't support enumerations, and calling an API often means looking up required constants in some form of documentation (not in the context-sensitive online help, however). The .NET Framework, on the other hand, provides much of the same functionality of the Windows API (including additional capabilities such as GDI+ functions) wrapped in objects that are more intuitive (usually) than API function calls, run as managed code, share the same type system as all other .NET applications, support type libraries, and have integrated help.
In general, you should use a .NET object instead of calling the Windows API whenever possible.
Perhaps the biggest drawback of using .NET Framework objects over the Windows API is that a wealth of books and sample code illustrates how to do things with the API (not to mention a lot of legacy code that uses the Windows API), while comparatively little details the equivalent functions using the .NET Framework. I expect this to change, perhaps even rather quickly. In the meantime, finding the appropriate .NET substitute for a Windows API call will largely be a process of discovery.
Private Declare Function GetSystemDirectory _ Lib "kernel32" _ Alias "GetSystemDirectoryA" _ ( ByVal lpBuffer As String , _ ByVal nSize As Long ) As Long Dim lngReturn As Long Dim strBuffer As String = "" Dim strPath As String Dim strSystemFolder As String ' Pad the buffer for the API call. strBuffer = strBuffer.PadLeft(255, "0") ' The API call returns the number of characters in the path. lngReturn = GetSystemDirectory(strBuffer, strBuffer.Length) strSystemFolder = strBuffer.Substring(0, lngReturn)
Dim strSystemFolder As String strSystemFolder = System.Environment.SystemDirectory()
I highly recommend you check out the documentation on System.Environment ; it supports a lot of useful properties and methods.
Any variable declared as Public within a class becomes a "property" of the class. (Actually, the behavior is similar to a property, but such a variable is called a field .) For example, suppose you have the following statement in the Declarations section of a class:
Public Length As Long
obj.Length = 255
Although this works, serious limitations make this approach less than desirable. These concerns include the following:
You can't execute code when a field value changes. For example, what if you wanted to write the new value of length to a database? Because the client application can access the variable directly, you have no way of knowing when the value of the variable changes.
There is no way to implement data validation on a field. For example, you couldn't prevent a user from passing a value to the
New to Visual Basic .NET is the ability to create a read-only field. You do this by using the ReadOnly keyword like this:
Public ReadOnly MyField As Integer
You might be tempted to do this; however, this approach has an important drawback. If you ever need to let a user modify the value or you ever need to run code when the property value is retrieved, you'll have to create a property in place of the field. Doing this will break compatibility with existing clients . If you create a property to begin with, you only have to change the implementation within the property procedure and you won't break compatibility in doing so.
When you need to create a member whose value can be changed, or a member that needs to execute code when called, you have no choice but to create a property rather than declare a public field. The best long-
Public Class Class1 ' Expose Length as a field. Public Length As Integer End Class
Public Class Class1 Private m_intLength As Integer Public Property Length() As Integer Get Return m_intLength End Get Set ( ByVal Value As Integer ) ' Data validation code goes here... m_intlength = Value End Set End Property End Class
At times, you might find yourself using some form of resource within a class, such as a COMM port. Whenever you open such a resource, be sure to close it appropriately. Usually, the best time to do this is as soon as you're finished with the resource. However, you might need to keep the resource open for the life of the object. In this case, your best bet is to close the resource in your object's
event. You might also consider implementing some
Public Overloads Sub Dispose() ' Let the ADO.NET objects go out of scope... End Sub
Public Overloads Sub Dispose() ' Close all open ADO.NET objects. m_dstContacts.Close() m_cnADOConnection.Close() End Sub
In my opinion, one of the most exciting new object-oriented features is the ability to overload procedures. When you overload a procedure, you create a procedure with the same name as an existing procedure, but one that accepts a different set of arguments. If you've ever had to come up with creative
to create procedures with the same name, each overloaded procedure must have a different argument list. The argument lists can
Public Sub DrawText( ByVal strText As String ) ' Code to draw the text goes here. End Sub Public Sub DrawNumber( ByVal sngNumber As Single ) ' Code to draw the number goes here. End Sub
Public Overloads Sub Draw( ByVal strText As String ) ' Code to draw the text goes here. End Sub Public Overloads Sub Draw( ByVal sngNumber As Single ) ' Code to draw the number goes here. End Sub
It's not required that you include the
keyword when defining overloaded procedures. However, it greatly adds to the readability of the code because it quickly
If you specify the Overloads keyword for one of a set of overloaded procedures, you must supply it for all of procedures with the same name.
Public Sub Draw( ByVal strText As String ) ' Code to draw the text goes here. End Sub Public Sub Draw( ByVal sngNumber As Single ) ' Code to draw the number goes here. End Sub
Public Overloads Sub Draw( ByVal strText As String ) ' Code to draw the text goes here. End Sub Public Overloads Sub Draw( ByVal sngNumber As Single ) ' Code to draw the number goes here. End Sub
As I mentioned earlier in the section on garbage collection, you can never be certain when an object will be
The only potential
disadvantageof traced garbage collection is the interval between the release of the last reference that can be reached by running code, and the moment when the garbage collector detects this condition. On a lightly loaded system with a great deal of memory, considerable time may elapse before your component's destructor is called.
Don't let the potential disadvantage text fool you—this is a serious issue that you need to take into account when designing and consuming objects. The solution to this problem is twofold:
Create a Dispose method for each of your objects and place cleanup code in that method.
Call the Dispose method on each and every object that has one, once you are finished with the object.
When creating shared
Some sources recommend adding a custom Dispose method only to components that use limited or expensive resources. This approach has a number of problems, including the following:
on objects that support it
is a must
, and you need to get into the habit of doing this. If some of your objects have a custom
method and some don't, you and other developers might
What happens if you modify a component such that you now need to execute in a custom
method when you didn't previously? If the object has already been distributed without a custom
method, all client code would have to be modified to call
. If you had previously implemented a custom
method—even if it didn't actually do anything—you could easily insert the necessary code without negatively
For these reasons, I highly recommend that you add custom
of your objects, whether the
methods actually do something or not. Also, if you
If your object holds references to other objects that have Dispose methods, you should call those methods from your own Dispose method.
Adding a Dispose method is easy; you just define the method like this:
Public Sub Dispose() ' Insert cleanup code here. End Sub
If your object inherits from System.ComponentModel.Component , the object already has a default implementation of Dispose , which is why I keep referring to the method you create as a "custom Dispose method." In this case, you'll have to overload the new method like this:
Public Overloads Sub Dispose() ' Insert cleanup code here. End Sub
The compiler will tell you if you need to overload the method, as shown in Figure 11-1.
When consuming COM objects from within Visual Basic .NET, pay careful attention to the resources used by the object. COM objects were built around a reference counting scheme that insured an object was destroyed when its last reference was released, and cleanup code was often placed in the class's
event. Because .NET uses garbage collection instead, you might run into problems with some COM objects not freeing their resources in a
The object-oriented features of Visual Basic .NET provide a mechanism for you to add constructors to your classes. In the past, a user had to create an instance of a class and then set values as shown here:
Dim objContact As New Contact() objContact.FirstName = "James" objContact.LastName = "Foxall"
One of the drawbacks to this approach was that you could not force the client to provide initial values in order to create an object. This
Dim objContact As New Contact("James", "Foxall")
You can even overload the constructor to allow different argument lists to be passed. For example, you could provide a generic constructor (allowing the user to use New while providing no arguments) and a specific constructor that accepts arguments. You could also leave out the generic implementation so that the user is forced to provide initialization data. Figure 11-2 illustrates the compile error that occurs if you attempt to create a new object instance without initialization data when using a class that requires a constructor.
To create a constructor, you define a procedure within the class called New , assigning it the appropriate access level. (For instance, you can create a Private constructor that can be called only from within the class itself.) For example, to create the constructor for the fictitious Contact class discussed earlier, you could use the following procedure:
Public Sub New ( ByVal FirstName As String , ByVal LastName As String ) ' Perform initialization code with the supplied parameters here. End Sub
Note that if you were to add this procedure to a class, it would force the client code to specify the initialization data; a client could not instantiate an instance of the class without the initialization data using a statement such as
Dim objContact As New Contact()
. To provide a generic implementation (one that requires no initialization data), you would have to add the following procedure in addition to the one shown above:
Public Sub New () ' Place code for generic initialization here. End Sub
You can do some interesting things with the various
Public Sub New () ' Call another constructor with default initialization values. MyClass .New("John", "Doe") End Sub
You cannot use the keyword Overloads on constructors, even though creating multiple constructors is indeed creating overloaded methods.
You can create as many constructors as you need to, as long as each has a unique argument list. For example, you could add the following constructor to the Contact class along with the two already defined:
Public Sub New ( ByVal ContactName As String ) ' Perform initialization code with a full name supplied. End Sub
You should consider adding a constructor to a class when
The class requires specific pieces of data. In this case, consider leaving out a generic implementation and supplying only constructors that require the needed information.
There exists common data used by the class. If client code typically sets the same properties each time an object instance is created, providing a constructor that accepts the data makes writing the client code a little easier and more efficient.
You need to implement some sort of instance counter. In this case, you could increment a counter in the constructor and decrement the counter in the
method (if you can be sure that clients will call
properly) or in the
method discussed in the
A constructor serves the same purpose as the Initialize event of Visual Basic 6—albeit with much more functionality and flexibility.
In Visual Basic 6, the visibility of a class was determined by its Instancing property. This has been changed in Visual Basic .NET. Now, the access modifiers you use for the class itself, as well as its constructors, determine the visibility of the class. Table 11-1 illustrates the Instancing values of Visual Basic 6 and their appropriate implementation in Visual Basic .NET.
Visual Basic 6 Instancing
Visual Basic .NET Implementation
Set the class declaration to Friend .
Set the class declaration to Public and all constructors ( Sub New ) to Friend .
Set the class declaration to Public , and declare the constructors as Public .
For example, to create a class available outside a project, but one where objects derived from the class can be
Public Class Contact Friend Sub New () ' Initialize the object here. End Sub End Class
This practical application doesn't include correct/incorrect examples.
If you developed classes in Visual Basic 6, you've undoubtedly used the
event at some point in time. The
event in Visual Basic 6 executes when the last reference to an object is released—right before the object is destroyed. This event was used to execute cleanup code for the object. As I explained earlier, Visual Basic .NET uses a garbage collection scheme to destroy objects and
Every class has a default implementation of Finalize , which is called by the garbage collector before actually destroying an object. Once again, you can never be sure when this will happen, but at least you can execute specific code when it does. If you don't have any code that needs to execute when an object is destroyed, there's no need to create a custom Finalize method in your class. In fact, it's desirable not to have a custom Finalize method because of the negative impact it can have on performance.
The reason that a custom
event negatively affects performance has to do with the way the garbage collector deals with objects that have a custom implementation of
. When such an object is created, the garbage collector places an entry in the
That said, if you positively must execute code when an object is destroyed (such as cleanup code to release any unmanaged resources still being held), a custom Finalize method is the way to go. To create a custom Finalize method in a class, declare a procedure like this:
Protected Overrides Sub Finalize() ' Cleanup code goes here. End Sub
You should avoid referencing other objects in your finalize code because the nature of garbage collection is such that finalizers aren't executed in any special order. If you reference objects in finalizing code, the objects might have already been finalized and collected, in which case the finalizing code would fail.
Note the use of the keyword
in this code sample. This
For an interesting experiment, create a class and add a custom Finalize method containing only a Stop statement. Then create a button on a form that instantiates an object derived from the class and immediately discards the object by setting the object variable to Nothing . See how long it takes before the Finalize method is actually called—you might be very surprised, as I was.
The garbage collector will scan for and reclaim resources automatically when a request for memory cannot be satisfied using available memory. There might be times, however, when you want to force garbage collection. For example, say your code just released a large number of objects that used a considerable amount of resources. If you have a beefy machine, garbage collection might not be triggered right away because the garbage collector might determine that your application still has enough memory available to it. However, you might be interested in reclaiming all of those resources right away and therefore would like to force a garbage collection. To force garbage collection, call the Collect method of System.GC like this:
As you can see, forcing garbage collection is a trivial task. As such, you might be tempted to do this regularly within your code. It's usually best to let the garbage collector determine when to reclaim resources, however. The garbage collector itself consumes processor time and other resources, and forcing it to collect at regular intervals when it's really not necessary to do so could negatively impact the performance of your application (and the entire system as well).
Whenever you write a set of code that repeatedly references the same object, consider placing the code in a With block. When you use a With block, your code will be more efficient, with the added bonus of less typing and more readability.
Private Sub btnAddNew_Click( ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnAddNew.Click ' Create a new contact record, populating it with the ' data entered by the user. m_rstContacts.AddNew() m_rstContacts.Fields("ContactName").Value = _ txtNewContactName.Text m_rstContacts.Fields("State").Value = txtNewState.Text m_rstContacts.Update() ' Show the newly created record. Call ShowCurrentRecord() End Sub
Private Sub btnAddNew_Click( ByVal sender As System.Object, _ ByVal e As System.EventArgs) _ Handles btnAddNew.Click ' Create a new contact record, populating it with the ' data entered by the user. With m_rstContacts .AddNew() .Fields("ContactName").Value = txtNewContactName.Text .Fields("State").Value = txtNewState.Text .Update() End With ' Show the newly created record. Call ShowCurrentRecord() End Sub
When creating a
block, use the
With frmCallingForm ' Create a random number for X coordinate of the text. intTextX = Int(((.ClientRectangle.Width - 20) - 0 + 1) _ * Rnd() + 0) ' Create a random number for Y coordinate of the text. intTextY = Int(((.ClientRectangle.Height - 20) - 0 + 1) _ * Rnd() + 0) End With
With frmCallingForm.ClientRectangle ' Create a random number for X coordinate of the text. intTextX = Int(((.Width - 20) - 0 + 1) * Rnd() + 0) ' Create a random number for Y coordinate of the text. intTextY = Int(((.Height - 20) - 0 + 1) * Rnd() + 0) End With
In previous editions of Visual Basic, it was recommended that you not nest With…End With structures. This is no longer taboo in Visual Basic .NET.