Francesco's blog

 Monday, May 29, 2006

I have been so busy in May that I couldn't update the blog, not even to mention that my latest Microsoft Press book had been released and is now available on all major US bookstores. With fewer than 600 pages, Programming Microsoft Visual C# 2005: The Base Class Library is surely the shortest book I wrote. Not only that: it's also the book that took me less time to wrote. In fact, this book is basically the translation to C# of the second half of my VB 2005 book, more precisely of the chapters that have to do more with the .NET Framework and less with the C# language itself. Here's the Table of Contents:

1. .NET Framework Basic Types
2. Object Lifetime
3. Interfaces
4. Generics
5. Arrays and Collections
6. Regular expressions
7. Files, directories, and streams
8. Assemblies and resources
9. Reflection
10. Custom attributes
11. Threads
12. Object serialization
13. PInvoke and COM interop

Even though the book isn't specifically on the C# language, it adequately covers most of the new features of C# 2.0, such as generics, iterators, and anonymous methods.

Why a book on the BCL? Well, in these years I realized that far too many developers focus solely on high-level features - such as Windows Forms, ADO.NET, and ASP.NET - and often fail to leverage the full potential from other portions of the .NET Framework. For example, I have seen many apps that use verbose and unefficient validation rules that might be replaced by a single regular expression. Or apps that could be written in a fraction of time (and lines of code) if the author had been conscious of the full potential of reflection and custom attributes. Not to mention the fact that new .NET 2.0 features, such as generics, could make things only worse.

In general I find that most books that are "translated" from a different programming language are disappointing, so you might wonder why this book should be different. First, when I signed the contract for my VB 2005 book I already knew that the book would have been translated to C#, thus I planned the book so that its structure wouldn't be too VB-centric. Secoond, in the last four years I have been using C# in virtually all my programming projects - in fact I have surely written more C# code than VB code. For this reason, you'll find that the C# code is carefully optimized to use all the usual C#-specific techniques, such as iterators and anonymous delegates.

Shortly I will prepare a home page for the book on this site, with a couple of sample chapters. In the meantime, you can read more about the book (and hopefully order it) on Amazon's home page.

5/29/2006 8:50:45 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, May 03, 2006

Yesterday I got an email from reader Claudio Fontana, with the following, deceiptively simple request: how can you avoid flickering while updating many controls on a form? The problem is especially serious when you need to add thousands of items to a ListView or a TreeView.

In VB6 this problem can be solved quite simply by temporarily setting the Visible (or Enabled) property to False for all the controls about to be updated: the control isn't actually hidden, yet the result of the update operation appears istantaneously when the property is reset to True. Just as interesting: the update operation is carried out much faster if the control is invisible, often twice as faster. Alas, this trick doesn't work in .NET, because as soon as you set the Visible property to False the control is immediately hidden. It's necessary to find another solution.

A few Windows Forms controls - namely the ListBox, ComboBox, ListView, and TreeView controls - do expose the BeginUpdate and EndUpdate methods, which allow you to "freeze" the control while you add items to it. Not only do they solve the flickering problem, they also speed up the update operation, tipically by a factor of 2.5x. However, if your form contains many controls that do NOT expose these methods, you must devise something else, and this was exactly the problem that Claudio submitted, after he unsuccessfully googled around on the 'Net looking for a solution.

The problem is quite intriguing, thus I decided to spend some time on it, until I came to the following solution. The idea is simple, and can be split in the following steps: (1) take a snapshot of the current form's appearance, by making a pixel-by-pixel copy into a bitmap, (2) create a PictureBox control as large as the form, and load the bitmap into the PictureBox, (3) add the PictureBox to the form's Controls collection and bring the PictureBox in front of all other controls, (4) while the user looks at the "frozen" image of the form, update your controls, using the BeginUpdate/EndUpdate mthods if possible to speed up execution, (5) when the update operation is completed, remove the PictureBox from the Controls collection, so that the user can now see the real form.

You just need one dozen statements to implement this algorithim, but I prepared a class to make the code more reusable and to ensure that it releases all resources correctly:

Public Class FormFreezer
  
Implements IDisposable

   ' The form being frozen
   Dim Form As Form
   ' the auxiliary PictureBox that will cover the form
   Dim PictureBox As PictureBox
   ' the number of times the Freeze method has been called
   Dim FreezeCount As Integer = 0

   ' create an instance associated with a given form
   ' and optionally freeze the form right away
   Public Sub New(ByVal form As Form, Optional ByVal freezeIt As Boolean = False)
      Me.Form = form
      If freezeIt Then Me.Freeze()
   End Sub

   ' freeze the form 
   Public Sub Freeze()
      ' Remember we have frozen the form once more
      FreezeCount += 1
      ' Do nothing if it was already frozen
      If FreezeCount > 1 Then Exit Sub

      ' Create a PictureBox that resizes with its contents
      PictureBox = New PictureBox()
      PictureBox.SizeMode = PictureBoxSizeMode.AutoSize
      ' create a bitmap as large as the form's client area and with same color depth
      Dim frmGraphics As Graphics = Form.CreateGraphics()
      Dim rect As Rectangle = Form.ClientRectangle
      PictureBox.Image = New Bitmap(rect.Width, rect.Height, frmGraphics)
      frmGraphics.Dispose()

      ' copy the screen contents, from the form's client area to the hidden bitmap
      Dim picGraphics As Graphics = Graphics.FromImage(PictureBox.Image)
      picGraphics.CopyFromScreen(Form.PointToScreen(New Point(rect.Left, rect.Top)), New Point(0, 0), New Size(rect.Width, rect.Height))
      picGraphics.Dispose()

      ' Display the bitmap in the picture box, and show the picture box in front of all other controls
      Form.Controls.Add(PictureBox)
      PictureBox.BringToFront()
   End Sub

   ' unfreeze the form
   ' Note: calls to Freeze and Unfreeze must be balanced, unless force=true 
   Public Sub Unfreeze(Optional ByVal force As Boolean = False)
      ' exit if nothing to unfreeze
      If FreezeCount = 0 Then Exit Sub
      ' remember we've unfrozen the form, but exit if it is still frozen
      FreezeCount -= 1
      ' force the unfreeze if so required
      If force Then FreezeCount = 0
      If FreezeCount > 0 Then Exit Sub

      ' remove the picture box control and clean up
      Form.Controls.Remove(PictureBox)
      PictureBox.Dispose()
      PictureBox = Nothing
   End Sub

   ' return true if the form is currently frozen
   Public ReadOnly Property IsFrozen() As Boolean
      Get
         Return FreezeCount > 0
      End Get
   End Property

   ' ensure that resources are cleaned up correctly
   Public Overridable Sub Dispose() Implements IDisposable.Dispose
      Me.Unfreeze(True)
   End Sub
End
Class

This is the C# version, translated from VB by Claudio:

public class FormFreezer: IDisposable
{

   // The form being frozen

   Form form;

   // the auxiliary PictureBox that will cover the form

   PictureBox pictureBox;

   // the number of times the Freeze method has been called

   int FreezeCount = 0;

 

   // create an instance associated with a given form

   // and freeze the form in base of flag freezeIt

   public FormFreezer(Form form, bool freezeIt)
   {

      this.form = form;

      if (freezeIt) this.Freeze();

   }

 

   // freeze the form 

   public void Freeze()
   {

      // Remember we have frozen the form once more

      // Do nothing if it was already frozen

      if (++FreezeCount > 1) 
         return;

      // Create a PictureBox that resizes with its contents

      pictureBox = new PictureBox();

      pictureBox.SizeMode = PictureBoxSizeMode.AutoSize;

      

      // create a bitmap as large as the form's client area and with same color depth

      Graphics frmGraphics = form.CreateGraphics();

      Rectangle rect = form.ClientRectangle;

      pictureBox.Image = new Bitmap(rect.Width, rect.Height, frmGraphics);

      frmGraphics.Dispose();

 

      // copy the screen contents, from the form's client area to the hidden bitmap

      Graphics picGraphics = Graphics.FromImage(pictureBox.Image);

      picGraphics.CopyFromScreen(form.PointToScreen(new Point(rect.Left, rect.Top)), new Point(0, 0), new Size(rect.Width, rect.Height));

      picGraphics.Dispose();

 

      // Display the bitmap in the picture box, and show the picture box in front of all other controls

      form.Controls.Add(pictureBox);

      pictureBox.BringToFront();

   }

 

   // unfreeze the form

   // Note: calls to Freeze and Unfreeze must be balanced, unless force=true

   public void Unfreeze(bool force)
   {

      // exit if nothing to unfreeze

      if ( FreezeCount == 0 ) 
         return ;

      // remember we've unfrozen the form, but exit if it is still frozen

      FreezeCount -= 1;

      // force the unfreeze if so required

      if (force) 
         FreezeCount = 0;

      if (FreezeCount > 0) 
         return;

      // remove the picture box control and clean up

      pictureBox.Controls.Remove(pictureBox);

      pictureBox.Dispose();

      pictureBox = null;

   }

 

   // return true if the form is currently frozen

   public bool IsFrozen
   {

      get { return (FreezeCount > 0); }

   }

 

   void IDisposable.Dispose()

   {

      this.Unfreeze(true);

   }

}

Using the FormFreezer class is quite simple. Here's a code sample, which assumes that it is located inside a form class so that the Me keyword points to the current form:

   Dim ff As New FormFreezer(Me, True)
   ' update controls here
  
' ...
  
ff.Unfreeze()

The class implements IDisposable, thus you can bracket the update code in a Using block, either in C# or in VB2005, and avoid an explicit call to Unfreeze:

   Using New FormFreezer(Me, True)
      ' Update controls here
     
' ...
  
End Using

Notice that calls to Freeze and Unfreeze must be balanced. If you call Freeze twice you then need two calls to Unfreeze to actually restore the updated form. This behavior allows you to call Freeze and then invoke a method that calls Freeze again and still have the code work correctly (provided that all methods use the same instance of the FormFreeze class).

5/3/2006 12:16:30 PM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [5]  | 
 Monday, May 01, 2006

Many, if not most, Windows Forms samples you can find on the 'net include one or more calls to unmanaged code in Windows DLLs, often in the form of calls to the SendMessage API methods to fix some of the (very few) missing features of .NET controls. The problem is, such a call to unmanaged code creates a problem when the program runs as a ClickOnce application, because it requires higher CAS privileges.

Even though this problem doesn't have a generic solution, when you just need to send a message to the control you are inheriting from, you can avoid an explicit call to SendMessage by invoking the protected DefWndProc method instead. For example, let's say that you are writing an enhanced ComboBox that exposes the TopIndex property, which can set or return the index of the first visible item in the list area. These two operations can be implemented by sending the control the CB_SETTOPINDEX or CB_GETTOPINDEX message, respectively. Here's how you can use the DefWndProc method instead of SendMessage:

Public Class ComboBoxEx
  
Inherits System.Windows.Forms.ComboBox

   Public Property TopIndex() As Integer
      Get
         Const CB_GETTOPINDEX As Int32 = &H15B
         Dim m As New Message()
         m.HWnd = Me.Handle
         m.Msg = CB_GETTOPINDEX
         Me.DefWndProc(m)
         Return m.Result.ToInt32()
      End Get
      Set(ByVal value As Integer)
         Const CB_SETTOPINDEX As Int32 = &H15C
         Dim m As New Message()
         m.HWnd = Me.Handle
         m.Msg = CB_SETTOPINDEX
         m.WParam = New IntPtr(value)
         Me.DefWndProc(m)
      End Set
   End Property

End Class

By the way, such an enhanced ComboBox can be useful when migrating a VB6 app to VB.NET. In fact, the VB6 ComboBox and ListBox controls expopse the TopIndex property, whereas under .NET only the ListBox control exposes this property. If you have any VB6 code that takes advantage of the ComboBox's TopIndex, the simplest approach is replacing the standard ComboBox with a ComboBoxEx instance.

5/1/2006 9:48:39 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [4]  | 
 Saturday, April 29, 2006

MsgHookX is an ActiveX DLL that allows VB6 developers to perform safe and efficient intra-applicaton subclassing. (In this context, intra-application sublclassing means that you can intercept any message that Windows sends to a window or a control in the current application, as opposed to a window created by another application, a much more difficult task.) I wrote this DLL many years ago, when VB5 was released, and mentioned it in several books and magazine articles. I made the DLL available on the vb2themax site, but I omitted to upload the DLL in this new, .NET-only site.

Well, given the many mails I continue to receive from readers, it seems that VB6 is still alive and in good shape, and many VB6 developers continune to happily subclass their controls. For this reason I decided to make the DLL available again on this site. Here's a summary of what it does:

  • safe subclassing: can be used within the IDE and in break mode without any risk of system crashes.
  • it provides the BeforeMessage and AfterMessage events for easy event-driven programming model
  • it additionally can notify incoming messages through the IMsgHookEvents secondary interface, for better performance and easier debugging (in some cases, events are inhibited in the IDE)
  • highest flexibility: you can decide to call the original window procedure yourself from within the BeforeMessage event/method and/or cancel the default processing for the message. You can also browse and modify the value that will be returned to the operating system.
  • the DLL's type library includes the definition of over 300 symbolic constants that define the most common Windows messages, so you don't have to use the API Viewer to include them in your applications.

Have fun with it, but - please, please! - turn to VB.NET as soon as you can, if you haven't yet.

4/29/2006 9:09:29 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 

I am working at the migration of a large VB6 project and at one point I needed to drop a few methods in the VB6 app, in such a way that the methods would be "invisible" to VB.NET. This action was necessary because these methods performed similarly to a .NET native method. Obviously, I had the option to manually delete the methods after the migration, but if their number is high and if you need to run the wizard more than once on the same VB6 app (as it happens frequently, in the process of preparing the app for the migration), then deleing these portions of code each time becomes a nuisance.

Apparently, you can't instruct the migration wizard to ignore one or more pieces of code. Nevertheless, the solution is quite simple: you just need to bracket these pieces of code - entire methods or just individual statements - in a #If Win32 ...#End If block. The Win32 compilation constant - that is probably ignored by most VB developers today - was introduced when Visual Basic 4 was released, and was recognized also by VB5 and VB6 (but not by any version of VB.NET). Visual Basic 4 is the only version of this language that is available in 16-bit and 32-bit edition, and this compilation constant (together with Win16) allowed to define blocks of code that were compiled under only one of those versions, while allowing to mantain a single source code for both. Therefore, the following VB6 code:

#If WIN32 Then
   Private Sub Do Something(ByVal n As Integer)   
      ' ...
   End Sub
#End If

is correctly migrated to VB.NET by the migration wizard as follows:

#If WIN32 Then
   Private Sub DoSomething(ByVal n As Short)
      ' ...
   End Sub
#End If

but this piece of code will be skipped over by the VB.NET compiler because the Win32 constant isn't defined under VB.NET.

This "feature" can actually create a problem if you are migrating a VB6 app that has been evolved from an older application that was originally written in VB4. In this case, in fact, it is possible that its source code contains one or more #If Win32 blocks. In most cases you *want* to migrate this code, but these portions will be ignored after the migration to VB.NET. If this is the case, you should then locate all the the occurrences of #If Win32 statements in the code and delete them before the migration.

4/29/2006 8:18:48 AM (GMT Daylight Time, UTC+01:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, March 06, 2006

I am reorganizing my MP3 collection and found that I needed to rename a large quantity of files. Of course, there are many free utilities that allow this operation - and that can use MP3 tags in the process - but I thought that I might write one myself. Thanks to regular expressions, the task shouldn't be that hard. In fact, in a few minutes I came up with the following console application. As you see, most of the code is used to extract and validate arguments on the command line:

Imports System.Text.RegularExpressions
Imports System.IO

Module Renx

  
Function Main(ByVal args() As String) As Integer
     
Console.WriteLine("RENX (C) Francesco Balena / Code Architects Srl")

      Dim recurse As Boolean = False
     
Dim renameMode As Boolean = False
     
Dim oldNamePattern As String = Nothing
     
Dim newNamePattern As String = Nothing

      ' analyze each argument
     
For Each arg As String In args
        
Select Case arg.ToLower()
           
Case "/s", "-s"
              
recurse = True
            
Case "/r", "-r"
              
renameMode = True
           
Case "/h", "-h"
              
Return ShowHelp(0)
           
Case Else
              
If oldNamePattern Is Nothing Then
                 
oldNamePattern = "^" & arg & "$"
              
ElseIf newNamePattern Is Nothing Then
                 
newNamePattern = arg
              
Else
                 
Return ShowHelp(1)
              
End If
        
End Select
     
Next

      ' check that we have both mandatory arguments
     
If oldNamePattern Is Nothing OrElse newNamePattern Is Nothing Then
        
Return ShowHelp(1)
     
End If
     
' create the regex and check that pattern syntax is ok
     
Dim reSearch As Regex
     
Try
        
reSearch = New Regex(oldNamePattern, RegexOptions.IgnoreCase)
        
' test the replace pattern as well
        
Dim tmp As String = reSearch.Replace("a dummy string", newNamePattern)
      
Catch ex As Exception
         Console.WriteLine(
"SYNTAX ERROR: {0}", ex.Message)
        
Return 3
     
End Try
     
Console.WriteLine()

      ' iterate over all files in current directory (and its subdirectories, if recurse mode)
     
Dim searchOpt As SearchOption = SearchOption.TopDirectoryOnly
     
If recurse Then searchOpt = SearchOption.AllDirectories

      Dim parsedFilesCount As Integer = 0
     
Dim renamedFilesCount As Integer = 0
     
Dim errorsCount As Integer = 0
     
For Each oldFile As String In Directory.GetFiles(Directory.GetCurrentDirectory(), "*.*", searchOpt)
         parsedFilesCount += 1
        
' the regex applies to name only
        
Dim oldName As String = Path.GetFileName(oldFile)
        
Dim ma As Match = reSearch.Match(oldName)
        
If ma.Success Then
           
' this is the new name
           
Dim newName As String = ma.Result(newNamePattern)
            Console.WriteLine(oldFile)
            Console.Write(
" => {0}", newName)
            renamedFilesCount += 1
           
' proceed with rename only if not in simulation mode
           
If renameMode Then
              
Try
                 
Dim dirName As String = Path.GetDirectoryName(oldFile)
                 
Dim newFile As String = Path.Combine(dirName, newName)
                  File.Move(oldFile, newFile)
              
Catch ex As Exception
                  Console.Write(
" -- ERROR: {0}", ex.Message)
                  errorsCount += 1
              
End Try
           
End If
           
Console.WriteLine()
        
End If
     
Next

      ' Display a report
     
If renameMode Then
        
Console.WriteLine("Summary: {0} parsed files, {1} renamed files, {2} errors", parsedFilesCount, renamedFilesCount, errorsCount)
     
Else
        
Console.WriteLine("Summary: {0} parsed files, {1} files affected", parsedFilesCount, renamedFilesCount)
         Console.WriteLine()
         Console.WriteLine(
"NOTE: Running in simulation mode. Specify the /R option to actually rename files.")
     
End If
     
' Return an error code
     
If errorsCount = 0 Then
        
Return 0
     
Else
        
Return 2
     
End If
  
End Function

   Function ShowHelp(ByVal exitCode As Integer) As Integer
     
Console.WriteLine()
      Console.WriteLine(
"Syntax: RENX <oldnamepattern> <newnamepattern> [/R] [/S] [/H]")
      Console.WriteLine(
" oldnamepattern : regex that selects the files to be renamed")
      Console.WriteLine(
" newnamepattern : regex that specifies how files must be renamed")
      Console.WriteLine(
" /R : rename files")
      Console.WriteLine(
" /S : iterate over subdirectories")
      Console.WriteLine(
" /H : display this help")
      Console.WriteLine(
"NOTE: By default the program runs in simulation mode, and just displays how files would be renamed.")
      Console.WriteLine(
" You must specify the /R option to actually rename the files.")
     
Return exitCode
  
End Function

End Module

At the very minimum, the RENX utility requires two arguments: a regex that specifies which files in the current directory (and its subdirectories, if you add the /S option) must be renamed, and a second regex that specifies how to rename the files that are matched by the first regex. The power of RENX is the fact that the first regex can (actually, must) specify one or more groups of characters, and these groups are then referenced in the second regex. For example, let's suppose that I have a folder with the following files:

        01 Speak to Me.mp3
        02 On the Run.mp3
        03 Time.mp3
        04 The Great Gig in the Sky.vb3
        05 Money.mp3
        06 Us and Them.mp3
        07 Any Colour You Like.vbr
        08 Brain Damage.mp3
        09 Eclipse.vb3

and that I want to rename them as follows:

        01 - Speak to Me - The Dark Side of the Moon.mp3
        02 - On the Run - The Dark Side of the Moon.mp3
        03 - Time - The Dark Side of the Moon.mp3
        04 - The Great Gig in the Sky - The Dark Side of the Moon.vbr
        05 - Money - The Dark Side of the Moon.mp3
        06 - Us and Them - The Dark Side of the Moon.mp3
        07 - Any Colour You Like - The Dark Side of the Moon.vb3
        08 - Brain Damage - The Dark Side of the Moon.mp3
        09 - Eclipse - The Dark Side of the Moon.vbr

Here's the RENX command that does it:

        RENX "(\d\d) (.+?)(\..+)"    "${1} - ${2} - The Dark Side of the Moon.${3}"

Notice that the first regex creates three groups by enclosing them in parenthesis: (\d\d) matches the song number, (.+?) matches the song title, and (\..+) matches the file extension, dot included. The second argument can then reorder these three groups, using the ${N}, where N is the position of the group as specified in the first regex. It is therefore to insert a dash after the song number, and the albumname after the song title.

Because the RENX utility is quite dangerous, by default it doe NOT rename the files, and it just lists how files would be renamed. To actually proceed with the rename operation, you must specify the /R option:

        RENX "(\d\d) (.+?)(\..+)"    "${1} - ${2} - The Dark Side of the Moon${3}" /R

That's all. You can play with the source code to extend the RENX utility as you prefer, and maybe turn it into a Windows Form application, or you can download the binary version from this link: Renx.zip (5.51 KB)

3/6/2006 5:30:10 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, February 28, 2006

A few minutes ago I made all the code samples for Programming Microsoft Visual Basic 2005: The Language available on the book home page. It's a 7.5M download that contains several thousand lines of carefully crafted, optimized, reusable source code, including the following:

  • a VB class that works with fractions
  • a base form for simplified data entry
  • a class to cache text files
  • several custom iterators for better For Each loops
  • tons of examples with generics
  • two expression evaluators, the former built on regexes, the latter implemented via on-the-fly compilation
  • a utility to display project statistics (a demo for regexes)
  • a RegexTester stand-alone utility, to help you build, test and compile regular expressions
  • an example of custom provider for My.Settings
  • an example of how you can intercept ANY event from ANY set of Windows Forms controls
  • attribute-based benchmarks
  • a complete infrastructure for writing Windows Forms plug-ins
  • an attribute-based library to write data-centric N-tier applications
  • several Visual Studio macros
  • Visual Studio visualizers for files, regex, and images
  • .... and a lot more

On the same page you can also find an errata document for typos and mistakes found after the book went to print.

Enjoy the code (and buy the book if you like it! :-) )

2/28/2006 7:10:13 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [4]  | 
 Thursday, February 23, 2006

The longer I work with generics, the more I like them, and I continue to discover new ways to simplify my code by using them. More specifically, I like the ability to write type-safe code that is also more concise, efficient, and (above all) readable, because I don't have to use tons of CType and DirectCast operators. Today I gathered the generic methods I use more frequently in the following module. They are tiny and simple, yet they save me a lot of time and code.

Module GenericFunctions

   ' Swap two variables
  
Public Sub Swap(Of T)(ByRef var1 As T, ByRef var2 As T)
     
Dim tmp As T = var1
      var1 = var2
      var2 = tmp
  
End Sub

   ' Type-safe version of the IIF function
  
' returns valueOnTrue if expression is True, else returns valueOnFalse
  
Public Function IIf(Of T)(ByVal expression As Boolean, ByVal valueOnTrue As T, ByVal valueOnFalse As T) As T
     
If expression Then
        
Return valueOnTrue
     
Else
        
Return valueOnFalse
     
End If
  
End Function

   ' Type-safe version of the Choose function
  
' returns the N-th element of a list of values, or the default value for T if index
  
' is less than 0 or higher than the number of values
  
Public Function Choose(Of T)(ByVal index As Integer, ByVal values() As T) As T
     
If index >= 0 AndAlso index < values.Length Then
        
Return values(index)
     
Else
        
Return Nothing
     
End If
  
End Function

   ' Return an array of the specified type
  
Public Function NewArray(Of T)(ByVal ParamArray values() As T) As T()
     
Return values
  
End Function

   ' Return the min value of a list
  
Public Function Min(Of T As IComparable)(ByVal firstValue As T, ByVal ParamArray values() As T) As T
     
Dim result As T = firstValue
     
For Each value As T In values
        
If result.CompareTo(value) > 0 Then result = value
     
Next
     
Return result
  
End Function

   ' Return the max value of a list
  
Public Function Max(Of T As IComparable)(ByVal firstValue As T, ByVal ParamArray values() As T) As T
     
Dim result As T = firstValue
     
For Each value As T In values
        
If result.CompareTo(value) < 0 Then result = value
     
Next
     
Return result
  
End Function

   ' Return True if a value is in specific range
  
Public Function InRange(Of T As IComparable)(ByVal testValue As T, ByVal minValue As T, ByVal maxValue As T) As Boolean
     
Return testValue.CompareTo(minValue) >= 0 AndAlso testValue.CompareTo(maxValue) <= 0
  
End Function

   ' Retrieve a dictionary element of a given type, or the provided default value if the element isn't found
  
' (two overloads)
  
Public Function GetDictionaryValue(Of TKey, TValue)(ByVal dict As Hashtable, ByVal key As TKey, ByVal defaultValue As TValue) As TValue
     
If dict.ContainsKey(key) Then
        
Return CType(dict(key), TValue)
     
Else
        
Return defaultValue
     
End If
  
End Function

   Public Function GetDictionaryValue(Of TKey, TValue)(ByVal dict As Dictionary(Of TKey, TValue), ByVal key As TKey, ByVal defaultValue As TValue) As TValue
     
' If the key is in the dictionary, the following statement stores the corresponding value
     
' in defaultValue, else it leave defaultValue unchanged
     
dict.TryGetValue(key, defaultValue)
     
Return defaultValue
  
End Function

End Module

Most methods are self-explanatory. One of the most useful ones is NewArray, which lets you create an array and pass it on-the-fly to a method. Let's say the the DoSomething method takes an array of Integers. These are the options you have in VB2005:

    ' 1. Create the array first, than pass it
    Dim values() As Integer = {1, 2, 3, 4, 5}
    DoSomething(values)

    ' 2. Create the array on the fly using the nearly-undocumented syntax
    DoSomething(New Integer() {1, 2, 3, 4, 5})

I often use the second syntax, but I noticed that relatively few developers know it. My code is much more readable with the NewArray method

    ' 3. Use the NewArray generic function to create the array on the fly
    DoSomething(NewArray(1, 2, 3, 4, 5))

The NewArray method proves to be quite useful also to build For loops whose index can take any sequence of values:

    ' Test whether "number" is a prime number in the range 1-1000
    For Each n As Integer In NewArray(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31)
       If (number Mod n) = 0 Then Console.Write("{0} is not prime", number): Exit For
    Next

2/23/2006 8:08:53 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, February 15, 2006

In the VB6 world - and in the COM world, in general - you "destroy" an object by simply putting it to Nothing, unless of course there are other variables that are pointing to that specific instance. The VB6 runtime invokes the Class_Terminate method, if it exists, then it deallocates all the resources belonging to the object, memory included. This is a recursive process, and destroys any object that is owned by the object being set to Nothing.

We know well that setting a VB.NET object variable to Nothing doesn't fire any event, because the Terminate event isn't supported in .NET. If the object implements the Finalize method, this method is invoked by the garbage collection, but this invocation occurs some time later. The time interval between the Set to Nothing statement and the invocation of Finalize depends on many factors, but it could be as long as a few minutes or even hours, if the application doesn't allocate many objects and doesn't stress the garbate collector.

This behavioral difference can be a serious issue when migrating a VB6 application to VB.NET. For example, if the code in Class_Terminate closes a database connection, or a file, or a serial port, or deletes confidential information from disk, or unloads a form, then the delay between the "logical" destruction of the object (i.e. the Set to Nothing) and its "physical" destruction (when the Finalize method runs) can compromise the correct working of the application after its migration to the .NET world.

Unfortunately, it isn't possible to have VB.NET "automagically" behave like VB6 in this respect. However, it is possible to take a step that can greatly reduce the problem, without much impact on the code structure. Once again, this is possible thanks to generics. To see how, create a Module and add the following code to it, so that the SetNothing method is visible to the entire application:

Public Sub SetNothing(Of T)(ByRef obj As T)
  
' Dispose of the object if possible
  
If obj IsNot Nothing AndAlso TypeOf obj Is IDisposable Then
     
DirectCast(obj, IDisposable).Dispose()
  
End If
  
' Decrease the reference counter, if it's a COM object
  
If Marshal.IsComObject(obj) Then
     
Marshal.ReleaseComObject(obj)
  
End If
  
obj = Nothing
End Sub

Next, you can apply the search-and-replace feature in Visual Studio to the code produced by the migration wizard, to replace all occurrences of the var = Nothing pattern with the SetNothing.(var) pattern. Obviously, this technique doesn't really solve all the abovementioned problems, because it works only with the variables that are explicitly set to Nothing via code, and doesn't work with variables that are implicitly cleared when they exit the current scope (for example at the end of the method).

Notice that the search-and-replace operation include a variable part - that is, the name of the instance that is set to Nothing - therefore making this replacementement automatically seems impossible or unpractical. But, fortunately, Visual Studio supports regular expressions in search-and-replace operations (as I explain