Building Distributed Applications with Visual Basic .NET Errata File
Lasted Edited: 5/2/2002
First Note: The note incorrectly states that there are
“over a thousand classes” in VS .NET. As of RC4 there were just over 6,500
classes in about 70 namespaces.
2nd Paragraph: There are now over 25 languages
targeted to the CLR.
3rd Paragraph: The paragraph states that
operating systems don’t know how to begin executing programs written in MSIL.
However, since the release of the book an update to Windows XP has enabled the
loader in XP to recognize .NET assemblies.
2nd full paragraph: This paragraph states that
the Dispose method calls the finalize method. Although possible in VB .NET this
is not the recommended approach since it doesn’t support C#, see the note below
on page 158.
3rd bullet at the bottom of the page: The second
phrase should begin “This includes a public key token” (since the public key is
actually 4K and would be too big to put in the manifest efficiently). In the
same paragraph the parenthetical remark “(also referred to as strong name)”
should be removed. Actually the combination of the version, culture, and shared
name information make up the strong name as correctly stated in chapter 5.
Last paragraph. Since this was written the CLR’s class
loader has been modified to not take the compatibility number into account. In
other words, the CLR will bind with any version if instructed to do so when the
version policy is appied. As a result the first and second paragraphs on page
39 are also inaccurate.
Note: The .NET Admin Tool is now called “Microsoft .NET
Framework Configuration” and can be found in the Administrative Tools group.
5th Paragraph. The paragraph implies that the GAC
is searched last. In reality for shared assemblies (those referenced with a
strong name), the GAC is searched first.
Last paragraph. The description of Administrator Policy is
out of place here since it was discussed earlier. This paragraph should be disregarded.
1st Paragraph. As mentioned previously, there
are actually over 6,500 classes and 70 namespaces.
The discussion of ReDim on this page should note that
arrays are also immutable and so redimensioning an array in VB actually allocates
new arrays which may be inefficient. For these types of dynamic allocation
operations you should use an ArrayList and then call its ToArray method to
return an array.
In the 2nd to last paragraph the statement is
made that consumers of a class must implement the event handlers for a
particular object if classes derived from the consumer are to be able to handle
them. This statement has caused some confusion.
The intended meaning is that if the consumer does not use
the WithEvents keyword when declaring the object then of course the derived
class will not be able to add the WithEvents keyword at a later time. However,
the derived class can still handle the event dynamically using the AddHandler
keyword like so:
Public Class HasEvent
Public Event MyEvent()
Public Sub EventMethod()
RaiseEvent MyEvent()
End Sub
End Class
Public Class myclass1
Protected e As New HasEvent()
End Class
Public Class myclass2 :
Inherits myclass1
Public Sub New()
AddHandler e.MyEvent, AddressOf myhandler
e.EventMethod()
End Sub
Public Sub myhandler()
' Will handle the event raised
End Sub
End Class
The discussion here on the Dispose and Finalize pattern is
incomplete at best and does not work with both VB and C#. A more complete
discussion follows.
A major design pattern that you’ll find in the .NET
Framework is that of Dispose and Finalize. Basically, you’ll find this pattern
in classes that acquire and hold on to resources such as file and window
handles or network and database connections. This is the case since the Common
Language Runtime (CLR) within which your .NET code runs implements
non-deterministic finalization, meaning that the CLR ultimately decides when
object instances are no longer able to be referenced and when those instances
are deallocated in memory. The process of reclaiming memory by deallocating
instances is referred to as garbage collection or simply GC. This approach is
obviously much different than in previous versions of VB where setting an
object to Nothing deallocated the object immediately causing its Terminate
event to run or in C++ where an object could be deallocated at any time causing
its destructor to run. Obviously, objects that hold onto resources such as file
handles or database connections need a way to release those resources in a
timely fashion, hence the dispose and finalize design pattern.
The idea behind this pattern is that the client decides
when the resources are no longer required and calls the Dispose or Close method
on the class. This method then cleans up the resources and ensures that the
object can no longer be used. However, adding a Dispose or Close method is not
sufficient in and of itself since if the client forgets to call it, the
resource may not then be released causing a leak in your application. To ensure
that resources are cleaned up, your classes can implement a destructor that
gets called automatically by the CLR when the GC process finally cleans up the
instance. As a result, in your classes you’ll want to implement a Dispose or
Close method along with a destructor as shown here.
Public
MustInherit Class QuilogyDataAccess : Implements IDisposable
Protected _disposed As Boolean = False
Protected Overridable Sub Dispose(ByVal
disposing As Boolean)
If disposing Then
' Call dispose on any objects referenced
by this object
End If
' Release unmanaged resources
End Sub
Protected Overridable Sub Dispose()
Implements IDisposable.Dispose
Dispose(True)
_disposed = True
' Take off finalization queue
GC.SuppressFinalize(Me)
End Sub
Protected Overrides Sub Finalize()
Me.Dispose(False)
End Sub
End
Class
Public
Class Customers : Inherits QuilogyDataAccess
Public Sub Close()
Me.Dispose(True)
End Sub
Protected Overloads Overrides Sub
Dispose(ByVal disposing As Boolean)
If disposing Then
' Call dispose on any objects referenced
by this object
End If
' Release unmanaged resources
MyBase.Dispose(disposing)
End Sub
Public Sub GetCustomers()
' Make sure the object has not been
disposed
If MyBase._disposed Then
Throw New
ObjectDisposedException("Customers", "Has been closed")
End If
' Do other work here
End Sub
End Class
You’ll notice that the QuilogyDataAccess class is a base (MustInherit, abstract in C#) class that ostensibly creates and holds on to a managed resource such as a database connection or an unmanaged resource such as a file or window handle. As a result this class exposes a Dispose method by implementing the IDisposable interface and includes a destructor implemented by overriding the Finalize method. You’ll notice that when the Dispose method inherited from the IDisposable interface is called it calls a second Dispose method passing it True. This second Dispose method cleans up the managed resources by calling their Dispose methods if passed True and then proceeds to deallocate unmanaged resources. When it is finished it returns and the original Dispose method then sets the protected disposed flag to True to indicate that the class has already been disposed. It then calls the static SuppressFinalize method of the GC class. This is done to prevent the Finalize method (the destructor) from running when the GC process next runs. Calling SuppressFinalize is simply an optimization that allows the instance to ultimately be deallocated sooner since all objects with a destructor are placed in a special queue that must first run their destructors before deallocating them.
While it may seem strange that you need to make this differentiation, it is important because in the event that the GC process ends up running the Finalize method you have no way of knowing whether or not the managed objects referenced by the class have already been finalized. This is the case since the order of finalization is not deterministic regardless of the order in which the objects were deemed unreachable by the GC. As a result you wouldn’t want to call their Dispose methods if they had already been deallocated. You’ll notice that the Finalize method simply executes the Dispose method passing in False to ensure that unmanaged resources are cleaned up in the event the Dispose method is never executed and the Finalize method is being run by the GC process. Incidentally, the pattern shown above works with C# as well where the destructor is identified using a ~ such as:
~ QuilogyDataAccess()
{
this.Dispose(false);
}
The other difference between C# and VB .NET is that
destructors in C# can only be called by the GC process and not directly by
client code whereas the Finalize method in VB .NET can be called by other code
in the class or its descendants. This is the reason that the destructor calls
Dispose and not vice versa.
The Customers class is then derived from QuilogyDataAccess
and can override the Dispose method in order to clean up its own resources by
calling Dispose on any objects it references before calling the Dispose method
of the base class. As with the base class the Dispose method of the derived
class accepts an argument that determines whether managed resources are cleaned
up in addition to unmanaged resources. By passing True into the method, as is
done by the Close method, the class will call the Dispose methods of any managed
objects it references in addition to deallocating unmanaged resources such as
file and window handles.
You’ll notice that the Customers class also exposes a
public Close method that allows clients to clean up the resources explicitly.
The Close method simply calls the Dispose method to do the work and can be used
in cases where the Close method makes more sense semantically. The GetCustomers
method of the Customers class shows how a method can then check to see if the
object has previously been disposed before allowing execution to continue. If
the object has already been disposed you can throw the ObjectDisposedException.
Note that if you wanted to recreate the resources in the event a method like
GetCustomers is called after the object has been disposed you would also need
to call the ReRegisterForFinalize method of the GC class to make sure the
instance will be finalized by the GC eventually in the event that Close or
Dispose is not called.
Clients using the Customers class can then instantiate the
class and work with it as normal, making sure to call the Close method when
finished or casting to the IDisposable interface like so.
Dim c As New Customers()
'
Do other work here
c.GetCustomers()
c.Close()
Or
Dim c As New Customers()
Dim d As IDisposable
'
Do other work here
c.GetCustomers()
d = c
d.Dispose()
Alternatively, the C# language includes the using statement
that automatically casts to the IDisposable interface and calls the Dispose
method when the block is exited like so.
using (Customers c = new Customers()) {
// Do other
work here
c.GetCustomers();
}
It should be mentioned that the compiler will only allow the using statement when the class to which it refers implements IDisposable interface.
Figure 5.2. While this UI element is really cool, it did
not make the final cut and so will not be in the released VS .NET. The reasons
I’ve heard for this are not very satisfying, especially one that said it was
taken out because C# did not have an equivalent. You now need to use the sn.exe
utility and the AssembyKeyFile attribute to make and embed shared name
information into the assembly.
1st Paragraph. See the note on page 41.
2nd full paragraph. See the note on page 41
The note on the bottom of the page talks about
DisposeObject. You can also use the Dispose method on the object instance to
clean up the proxy and COM+ context.
Just as with the previous note, the first paragraph on this
page omits mentioning that the Dispose method of the object will also place it
back in the pool immediately.
The OutputCache page directive shown in the snippets on
these pages should have a % in them as in:
<%@ OutputCache Duration=”60” Location=”Server” %>
Page 626
On this page the code snippet that shows handling the event
for the Timer object shows the event name as “Tick”. In the RTM the event name
was changed to “Elapsed” so the code snippet should be:
AddHandler mProcess.Elapsed, AddressOf OnTimer