Francesco's blog

 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 here), therefore you can search for the <{:i} = Nothing string and specify the SetNothing(\1) string in the Replace with field. Et voilà :-)

NOTE for those who are unfamiliar with regexes: the Find What string specifies that you want to search for a variable name (:i) that is located at the beginning of a word (<); curly braces in {:i} cause the variable name to be tagged, so that you can refer to the variable name in the Replace With regular expression, by means of the \1 placeholder. That's it.

2/15/2006 7:23:34 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, February 12, 2006

Great news for yours truly.

Practical Guidelines and Best Practices for Visual Basic and Visual C# Developers won an Excellence Award from the Society of Technical Communication, which selects the best books, sites, and coursewares published in the year. Not just programming-related or computer-related publishing, but technical publishing in the wider sense. As a matter of fact, among the books that won the award, only about ten have to do with software. And the book I co-authored with Giuseppe Dimauro (the other Italian RD) is the only book from Microsoft Press that won the award. I guess it's something we can be quite satisfied of :-)

It's an important acknowledgement and I am especially happy because it's the first book I wrote that isn't part of the "Programming Visual Basic" series, which now counts four editions (VB6, 2002, 2003, and 2005). The "Practical Guideline" book is a truly unique book, the first and only coherent collection of guidelines for .NET developers.

 

2/12/2006 8:51:08 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
 Sunday, January 29, 2006

In this period I am actively researching the many problems you face when migrating VB6 apps to .NET. I don't like *migrating* applications, because I always prefer to rewrite them from scratch to leverate all the features of .NET and above all because the conversion wizard doesn't do a great job and produces ugly and non-maintenable code. Better, the wizard does a decent job, as long as it doesn't have to handle incompatibilities between VB6 and VB.NET.

Some of these incompatiblities, however, can be solved with a bit of imagination, especially now that VB2005 has features that weren't available before. For example, consider the "classic" problem of converting arrays with a lower index other than zero, a problem that has bothered all VB6 developers trying to porting complex code to VB.NET. Let's say you have this code:

      Dim arr(1 to 10) as Integer
      Dim i As Integer, prod As Integer, v As Variant
      For i = LBound(arr) To UBound(arr)
         arr(i) = i
      Next
      For Each v in arr
         prod = prod * v
      Next

The conversion wizard will replace the index "1" with the index "0", therefore the array has one more element. It's evident that, at the end of execution, the value of prod will be zero, whereas it should be equal to the factorial of 10. This sort of bug is quite subtle, and in practice you're forced to scrutinize your source code and re-test the application entirely. A better, manual approach consist of fixing the Dim statement to "shift" the array so that its first non-empty element has zero index, and then modify ALL the references to the elements of the array, to account for the shift:

      Dim arr(0 to 10-1) as Integer     
      Dim i As Integer, prod As Integer
      For i = LBound(arr) To UBound(arr)
         arr(i - 1)  = i
      Next

Unfortunately, also this approach requires a lot of time and attention, and in some cases it can't be used, for example when the array is passed to a method that must work with arrays of any type (and whose code doesn't know that it has to shift the index). In yet other cases, the VB6 source code might use the value returned by LBound or UBound, and this code wouldn't work well after the migration.

The question is therefore: is it possible to convert this code to VB2005 without having to worry about all these issues? The solution has been relatively simple, thanks to generics and a few tricks with inheritance:

' Base class

Public Class VBArrayBase
  
Protected Friend lowerIndex As Integer
  
Protected Friend upperIndex As Integer
End Class

' One dimensional array of type T

Public Class VBArray(Of T)
  
Inherits VBArrayBase
  
Implements IEnumerable

   Dim items() As T

   Sub New(ByVal lowerIndex As Integer, ByVal upperIndex As Integer)
     
Me.lowerIndex = lowerIndex
     
Me.upperIndex = upperIndex
     
ReDim items(upperIndex - lowerIndex)
  
End Sub

   Default Property Item(ByVal index As Integer) As T
     
Get
        
Return items(index - lowerIndex)
     
End Get
     
Set(ByVal value As T)
         items(index - lowerIndex) = value
     
End Set
  
End Property

   Public Function GetEnumerator() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
     
Return items.GetEnumerator()
  
End Function

End Class

Notice how simply the class provides support for For Each loops: it just has to return the IEnumerator object of the inner array. At this point I just needed to extend the LBound and UBound support to the new class. To do so, I created the following public module:

Public Module ArrayFunctionsVB6
   Function LBound(ByVal arr As Array, Optional ByVal rank As Integer = 1) As Integer
     
Return Microsoft.VisualBasic.Information.LBound(arr, rank)
  
End Function

   Function UBound(ByVal arr As Array, Optional ByVal rank As Integer = 1) As Integer
     
Return Microsoft.VisualBasic.Information.LBound(arr, rank)
  
End Function

   Function LBound(ByVal arr As VBArrayBase, Optional ByVal rank As Integer = 1) As Integer
     
If rank = 1 Then
        
Return arr.lowerIndex
     
Else
        
Throw New IndexOutOfRangeException()
     
End If
  
End Function

   Function UBound(ByVal arr As VBArrayBase, Optional ByVal rank As Integer = 1) As Integer
     
If rank = 1 Then
        
Return arr.upperIndex
     
Else
        
Throw New IndexOutOfRangeException()
     
End If
  
End Function
End
Module

The module must expose two overloads for each method, one overload for standard arrays and the other for the new VBArray(Of T) class. Alas, you can't have a project that references two distinct modules - one in the VB compatiblity library and one in another DLL - where each module contains a different overload of the same method. In this case, only one of the two methods is visible to the main program.

Another interesting detail: the code inside the LBound and UBound methods needs to access the Friend members of the VBArray(Of T) class, but these methods can't have VBArray(of T) in the parameter list, because they aren't generic methods. This is the reason why I have the VBArray(Of T) class derive from VBArrayBase, where these Friend members are defined.

Thanks to the VBArray(Of T) class and the ArrayFunctionsVB6 module, you can migrate the VB6 code by changing only the DIM statement, as follows:

     Dim arr As New VBArray(Of Short)(1, 10)      ' Short instead of Integer

The remainder of the code will work flawlessly, exactly as in VB6, including the For Each loop and calls to LBound and UBound. Seeing is believing! :-)

1/29/2006 3:30:03 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, January 16, 2006

If I could get istantaneous results for the following simple two-question survey

  1. Is Visual Studio the application that you use most often?
  2. Did you ever use regex searches in the VS Find dialog box?

I'd bet that 80% of you would answer YES to the first question, but 99% of you would answer NO to the second question, which would be a rather weird result. Regex searches are among the most powerful VS features, yet few developers use them or even know that they exist.

IMHO, the real problem is that VS regex's syntax is completely different from the syntax you use with the Regex class, therefore using this feature requires that you learn yet another regex dialect. This is a bit too much for most developers. Microsoft should allow the standard regex syntax in this dialog: they could implement this change very easily and in a short time, without caring about backward compabibility issues.

While waiting for Microsoft to offer this little-big innovation, you can have fun with what you have today. Here are a few examples, excerpted from my new book Programming Microsoft Visual Basic 2005: The Language:

:i = :z   Search assignments of an integer constant (:z) to a variable (:i). In VB, but more rarely in C#, it can deliver false matches, when the = operator is used in an expression.

:i = :q   Search assignments of a quoted string constant (:q) to a variable.

(Dim|Private|Public) :i As String   Search for variable declarations of string type (VB only). You can easy adapt it to other data types.

Dim <(:Lu(:Ll)*)+> As   Search for local VB variables that use a PascalCase naming convention and therefore violate Microsoft's guidelines. (Local variables should use the camelCase convention.)

^:b*'.+\n   Search for comment lines in VB, that is, lines that begin with an apostrophe. (It doesn't consider the REM keyword.) You can replace the apostrophe with // to use this search pattern in C# as well.

Dim {:i} As (.|\n)#<\1>    Highlights the portion of code between the declaration of a local variable and the first occurrence of that variable in the method. You can repeat this search for all the local variables in a method and check whether you should refactor your code by moving the declaration closer to where the variable is used for the fist time. (See effect in figure below.)

1/16/2006 5:13:02 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, January 07, 2006
In a week or two my new book, Programming Microsoft Visual Basic 2005: The Language will be available on Amazon and other online stores. Towards the end of January it should be available in most bookstores in the US.

In the meantime, you can get a taste of how the book looks like by reading two online chapters: Chapter 11, "Generics", and Chapter 18, "Reflection".

1/7/2006 4:53:16 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
 Wednesday, December 28, 2005

Today I spent a few minutes trying to understand the behavior of nullable types in C#, in particular how comparison operators evaluate their result. An asimmetry exists between the == and != operators and all the other comparison operators. In fact, the equal and not-equal operators work perfectly even when the two operands are both null, as in the following code:

int? x1 = null;
int? x2 = null;
Console.WriteLine(x1 == x2);    // => True
Console.WriteLine(x1 != x2);     // => False

The remaining comparison operators, however, don't deal with null values in the same correct way. If either operand is null, these operators always return false. This inconsistent behavior brings to weird situations, in which two values can satisfy the "equal to" relation but not the "equal to or greater than" relation:

int? x1 = null;
int? x2 = null;
Console.WriteLine(x1 == x2);    // => True
Console.WriteLine(x1 >= x2);    // => False

To compare nullable values in a coherent way, you should use the Nullable.Compare static method, which considers null as less than any other value:

switch (Nullable.Compare(d1, d2))
{
  
case -1:
     
Console.WriteLine("d1 is null or is less than d2");
     
break;
  
case 1:
     
Console.WriteLine("d2 is null or is less than d1");
     
break;
  
case 0:
     
Console.WriteLine("d1 and d2 have same value or are both null.");
     
break;
}

In some cases, you can compare two nullable values by using the lowest value in the range in lieu of the "unknown" state, as in:

Console.WriteLine(x1.GetValueOrDefault(int.MinValue) >= x2.GetValueOrDefault(int.MinValue));

(Of course, you can use this approach only if you're sure that operands can be assigned the int.MinValue value.) Besides being available in VB2005 as well, a minor advantage of these techniques is that they generate fewer IL code that is also slightly faster than the code produced by comparison operators. These operators, in fact, always invoke the GetValueOrDefault AND the HasValue property behind the scenes.

C#
12/28/2005 6:46:25 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [3]  | 
 Friday, December 16, 2005

As I anticipated yesterday, here is the TOC of my newest Programming Microsoft Visual Basic 2005, to be available in stores in early January.

Part I : The Basics
 1. Introducing the .NET Framework
(12 pages): a quick overview of basic concepts in .NET programming.
 2. Basic Language Concepts (70 pages): modules, classes, variables, arrays, operators, etc. plus what you need to know about inheritance and attributes so that you can read next chapters, before chapter 8 and 19.
 3. Control Flow and Error Handling (40 pages): If, Select, For, For Each and other basic statements; error handling, with many not-so-obvious techniques to improve code efficiency and programming style.

 4. Using Visual Studio 2005 (56 pages): the many new features of VS2005 IDE, plus many old features that not all developers know; how to write a code snippet for VS2005; templates, refactoring, and a brief but intense tutorial on VS macros.

 5. Debugging and Testing (56 pages): breakpoints and tracepoints, data tips, how to write a cusom visualizers, trace commands and trace listeners (including custom listeners), benchmarks and profiling, unit testing, and code coverage.

Part II : Object-Oriented Programming
 6.
Class Fundamentals (42 pages): the "usual" story about classes, methods, properties, etc. plus the new partial classes and operator overloading, all peppered with the description of relatively unknown programming techniques.
 7. Delegates and Events (26 pages): a small chapters with many details and secrets on how to use these VB features in real-world apps. It includes the new custom events.
 8. Inheritance (34 pages): inheritance at its best, including visual (form) inheritance and many real examples.

 9. Object Lifetime (28 pages): everything you might need to know about garbage collection, the Dispose/Finalize pattern, weak references, GC generations, object resurrection, and other advanced techniques that can take your app to the next level
10. Interfaces (28 pages): how to define a custom interface and, above all, how to leverage those that .NET provides you with, such as IComparer and IEnumerable.
11. Generics (40 pages): half of what you want to know about this new great .NET 2.0 feature (the second half is in chapter 13), including constraints, nullable types, and many examples of programming techniques that are based on generics.

Part III : Working with the .NET Framework
12. .
NET Basic Types (50 pages): working with strings, numbers, and dates at their best, including many little/big new features of .NET 2.0
13. Arrays and Collections (53 pages): arrays, jagged arrays, "traditional" and generics collections, plus many tricks for writing less code that runs faster.
14. Regular Expressions (40 pages): a reference of regex syntax, plus many practical examples on data validation, data parsing, and even code parsing. If you aren't familiar with regexs you are missing a great occasion for writing better code in less time.
15. Files and Streams (42 pages): an overview of all the types in System.IO and the many new features in .NET 2.0, including ACL support, compressed streams, and the TextFieldParser type.
16. The My Namespace (48 pages): how to use the My namespace and how to extend it as you need.
17. Assemblies and Resources (44 pages): despite of their importance, resources (either simple or localized) are used rarely and unproperly by most developers; this chapter includes a complete description of the many important features added to NGEN.

Parti IV : Advanced Topics
18. Reflection
(58 pages): there is a lot to say about reflection; among the many examples I wrote an app that generates code on the fly, a scheduler for undoable actions, and a universal comparer class.
19. Custom Attributes (46 pages): this chapter includes a few complete and nontrivial examples of how a custom attribute can make your coding simpler, for example by means of Windows Forms plugins and a framework for n-tier apps.

20. Threads (54 pages): the Thread object, asynchronous delegates, thread pool, the SyncLock statement, all the synchronization types, including the new Semaphore. Plus a section on threading in Windows Forms aoos.
21. Object Serialization (32 pages): binary and SOAP serialization, version-tolerant serialization in .NET 2.0, the new attributes for serializatoin, custom serialization, serialization surrogates, the IObjectReference interface, and more.
22 PInvoke and COM Interop (40 pages): How to interact with unmanaged code: calling "classic" DLL and Windows API methods; using COM components (including the new registration-free components); writing .NET components that can be used from COM apps.

As I already explained, this book isn't a mere VB 2005 reference. Better, in addition to being a complete reference book, it is a digest of the many programming techniques that you can implement by leveraging the features of the language and the .NET Framework 2.0, including generics, threads, reflection, custom attributes, serialization, delegate, regular expressions, and more. All descriptions aim to writing faster and more robust code. I looked hard for a similar book on the market before writing this one. I believe I finally wrote a book that does VB justice.

It has been a real tour de force, which kept me busy from May, summer included. Today I have completed the very last edit to PDFs, then everything goes to the printer!

12/16/2005 2:32:12 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Thursday, December 15, 2005

Many readers are sending emails asking whether I was writing the 2005 edition of Programming Microsoft Visual Basic .NET. The answer is "yes and no".

Yes, I am writing the new edition of this book, updated to Visual Basic 2005.

No, I am not really writing a new edition of that book. The book I am finalizing this week is actually a brand new book, titled Programming Microsoft Visual Basic 2005: The Language. I decided to keep the title similar enough to the original one, to emphasize that the author is the same and that the approach is similar; however, I decided to added "The Language" postfix, to ring a bell in the mind of potential buyers who might otherwise believe that this is "just" an update to VB2005 and .NET 2.0.

The new book focuses only on the VB language and the .NET Base Class Library (BCL). It covers both old and new keywords as well as topics such as .NET data types, arrays and collections, streams, reflection, serialization, threading, PInvoke and COM interop. It does not cover high-level stuff such as Windows Forms, ADO.NET, and ASP.NET, though.

I thought hard about the Table of Contents of this new book, literally for months. In the previous edition I managed to squeeze in "only" 1400 pages virtually anything you need to work with .NET Framework 1.1, including advanced topics such as serviced components and remoting, Windows Forms and Web Forms custom control creation, security, and so forth. However, .NET 2.0 is much more complex that 1.1, and I estimated that I would have either needed to split the book in two volumes or be less specific on most topics. Both choices were unsatisfactory to me.

Also, I couldn't help noticing that there are too many great books around about Windows Forms or ASP.NET programming, and a single core-reference book is bound to be less complete than those books with a narrower focus. Granted, a book from a single author and that covers all these topics can offer a unified view of what is important in .NET programming, but I am sure that developers who are deeply interested in a specific area will buy a book that specializes in that area.

While I was taking note of what else could be found on bookstore shelves, I found out that all these high-level books often overlook the basics, for example out to get the best out of the language or basic data types such as DateTime, arrays, and collections. Most .NET developers know how to write great Windows Forms or ASP.NET applications, yet they don't know how to optimize string-intensive programs effectively. And I am not talking about the usual String vs. StringBuilder example, I am thinking of techniques such as this, this, or this. Another example is memory optimization: you can speed up your code by an order of magnitude using caching techniques based on the WeakReference type, or by means of a correct Dispose-Finalize pattern. Not to mention advanced techniques based on delegates and reflection, such as this, this,or this.

In the end, I realized that I could write a very good book on just the Visual Basic language and the most important facets of the BCL. Rather than (or in addition to) being a plain reference for VB keywords and .NET types, this book is more similar to a complete compendium of programming techniques that you can implement with these features. For example, there is one entire chapter devoted to custom attributes, with a few advanced examples of how they can help you in the design of your n-tier apps. In other words, instead of just listing what are your tools this book will explain how to leverage them using intermediate-to-advanced techniques, including nonobvious techniques based on generics, on-the-fly compilations, advantage use of delegates and custom events, and so forth.

Another important topic that Programming Visual Basic 2005: The Language book covers is Visual Studio and how to take advantage of its many editing and testing features. The book includes two long chapters (112 pages in total) which covers basic and advanced topics, including macro creation, unit testing (with Team System), debugging techniques, and more. I have never found a language book that focuses on productivity inside the IDE and I hope this new book fills this gap.

The book consists of 22 chapters, for more than 1000 pages. I'll publish its Table of Contents in another post very soon, hopefully tomorrow.

12/15/2005 1:56:08 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, December 14, 2005

In a previous post I introduced a tiny utility to clean up Visual Studio projects. I have introduced minor fixes to it since then, so this is the most recent version, which also deletes *.suo and *.user files and produces a report of all the files and folders that couldn't be deleted for any reason:

Imports System.IO

Module Module1
  
Sub Main(ByVal args() As String)
      Console.WriteLine(
"VSProjCleaner tool (C) 2005 Francesco Balena, Code Archirects")
     
If args.Length = 0 Then
        
Console.WriteLine("Removes BIN and OBJ folders, .suo and .user files from Visual Studio projects")
         Console.WriteLine()
         Console.WriteLine(
" SYNTAX: VSProjCleaner dirname")
         Console.WriteLine()
        
Return
     
End If
     
Console.WriteLine()

      ' Use current directory if no argument has been specified
     
Dim rootDir As String = Directory.GetCurrentDirectory()
     
If args.Length > 0 Then rootDir = args(0)
     
' Read all the folder names in the specified directory tree
     
Dim dirNames() As String = Directory.GetDirectories(rootDir, "*.*", SearchOption.AllDirectories)
     
Dim errorsList As New List(Of String)

      ' delete any .suo and vbproj.user file
     
For Each dir As String In dirNames
        
Dim files As New List(Of String)
         files.AddRange(Directory.GetFiles(dir,
"*.suo"))
         files.AddRange(Directory.GetFiles(dir,
"*.user"))
        
For Each fileName As String In files
           
Try
              
Console.Write("Deleting {0} ...", fileName)
               File.Delete(fileName)
               Console.WriteLine(
"DONE")
            Catch ex As Exception
               Console.WriteLine()
               Console.WriteLine(
" ERROR: {0}", ex.Message)
               errorsList.Add(fileName &
": " & ex.Message)
            
End Try
         
Next
      
Next

      ' Delete all the BIN and OBJ subdirectories
      
For Each dir As String In dirNames
         
Dim dirName As String = Path.GetFileName(dir).ToLower()
         
If dirName = "bin" OrElse dirName = "obj" Then
            
Try
               
Console.Write("Deleting {0} ...", dir)
               Directory.Delete(dir,
True)
               Console.WriteLine(
"DONE")
            
Catch ex As Exception
               Console.WriteLine()
               Console.WriteLine(
" ERROR: {0}", ex.Message)
               errorsList.Add(dir &
": " & ex.Message)
            
End Try
         
End If
      
Next
      
Console.WriteLine()
      Console.WriteLine(
New String("-"c, 60))
      
If errorsList.Count = 0 Then
         
Console.WriteLine("All directories and files were removed successfully")
      
Else
         
Console.WriteLine("{0} directories or directories couldn't be removed", errorsList.Count)
         Console.WriteLine(
New String("-"c, 60))
         
For Each msg As String In errorsList
            Console.WriteLine(msg)
         
Next
      
End If
   
End Sub
End
Module

You can download the binary version here: VSProjCleaner.exe (24 KB) 
or the complete solution here: VSProjCleaner_Source.zip (15.08 KB)

Using this tool couldn't be simpler: just run it from the command window and pass a folder name as an argument, enclosing it in double quotes if it contains spaces:
                            VSPROJCLEARNER "c:\my projects\testproj"
The tool recursively visits all the folders and deletes all BIN and OBJ directories, and deletes *.suo and *.user files. At the end of the process it lists all the folders and files that couldn't be removed, if any.

DISCLAIMER: I have tested this code against my own projects and it always worked correctly, but I don't should be held responsible for any loss of important files (for example, a .user file that has nothing to do with a Visual Studio project.). For this reason, I urge you to apply this tool only to a COPY of your projects

12/14/2005 9:58:58 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [3]  | 
 Thursday, December 08, 2005

Last spring I co-authored this book, Practical Guidelines and Best Practices for Microsoft Visual Basic .NET and Visual C# Developers, arguably the longest title in Microsoft Press's history. The book is a reasoned list of guidelines that all .NET developers should follow, actually is by far the largest collection of its kind you can find anywhere. It covers language syntax, memory usage, Windows Forms and ASP.NET applications, security, and more.

Unlike most other similar collections, though, we clearly divide the "rules" in guidelines (naming guidelines, comment usage, etc.) and best practices. The difference is subtle but important: most guidelines are primarily a style matter, whereas best practices impact the scalability, the speed, or the robustness of your application. This means that our guidelines are arbitrary and in fact we often offer alternate rules and clearly explain the pros and cons of each style.

You can learn more about the principles we used in the book's Introduction and in John Robbins's Foreword. (Unlike most foreword writers, John actually read each and every page in the manuscript and gave us some great advice about improving it.) Or click the figure to jump to the book's home page, where you can read three sample chapters and download the book's source code.

Today I have uploaded a 30-page Word document that contains a summary of all the rules covered in the book, orderly grouped by topic and with a reference where in the book each rule is explained. You can edit this document as you see fit, delete or edit the guidelines you aren't interested in, and so forth. We routinely use this document in internal code reviews or when we consult at customers' places, so we hope it will be useful to you as well.

P.S. You must register to access this material. We swear we'll never send you anything that vaguely resemble spamming, just 100% technical contents!

12/8/2005 2:50:28 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, December 03, 2005

Consider the following code, that converts all the elements of an Int32 array into the corresponding hex value:

' VB
Dim intArray() As Integer = {4, 6, 9, 10, 99, 233, 34, 88, 189}
Dim hexArray(intArray.Length - 1) As String
For i As Integer = 0 To intArray.Length - 1
   hexArray(i) = intArray(i).ToString(
"X")
Next

// C#
int[] intArray = {4, 6, 9, 10, 99, 233, 34, 88, 189};
string[] hexArray = new string[intArray.Length];
for ( int i = 0; i < intArray.Length; i++)
{
   hexArray[i] = intArray[i].ToString(
"X");
}

The question is: how can you make this code more concise in .NET 2.0? The first answer that might come up is to use the Array.ConvertAll method together with a C#'s anonymous method:

string[] hexArray = Array.ConvertAll<int,string>(intArray, new Converter<int,string>(
  
delegate(int n) { return n.ToString("X");}));

Actually, you can write even more concise code if you remember than the Microsoft.VisualBasic library already contains the Hex method, which matches the signature of the Converter<int,string> delegate. Using this method and delegate inference, you can shrink the code to:

' VB
Dim hexArray() As String = Array.ConvertAll(Of Integer, String)(intArray, AddressOf Hex)
// C#
string[] hexArray = Array.ConvertAll<int,string>(intArray, Microsoft.VisualBasic.Conversion.Hex );

I am certain that few C# developers will use this trick, but I thought it was worth mentioning. (Of course, you must add a reference to the Microsoft.VisualBasic.dll assembly if you work with C#.) The key idea, however, is that in some cases you don't need to write an anonymous method to accomplish a given task, because often you can find what you're looking for in the .NET Framework. For example, you can display all the elements of an array in the Console window with just one statement:

' VB
Array.ForEach(hexArray, AddressOf Console.WriteLine)
// C#
Array.ForEach(hexArray, Console.WriteLine);

There are many other methods in the VB library that you can use to convert all the elements of an array or a generic List, including UCase, LCase, LTrim, RTrim, and Trim.

12/3/2005 2:17:40 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Thursday, December 01, 2005

One of the .NET Framework features that fascinate me most is regular expressions, which I often use to simplify and speed up my applications. Well, at least this is what I believed until some time ago, when I was busy writing the forthcoming Programming Microsoft Visual Basic 2005: The Language (due in mid-January). This book is a core reference on the VB language and includes a section on the LIKE operator, which in recent years a overlooked in favor of regexes. I (mistakenly) assumed that the Like operator internally used the Regex classes, therefore surely it would have been slower. After all these years, I should have learned that I should never jump to conclusions without testing and benchmarking my code accurately. .

Let's say that you must check that a string has 9 characters, the first of which must be an uppercase "A" and the last four chars must be digits. This is how you'd perform this test with a regex:

Dim re As New Regex("^A....\d\d\d\d$")

and here's the version that uses the Like operator:

If teststring Like "A????####" Then Match = True

Surprise! Putting this code in a loop (but leaving the creation of the regex out of the loop) and using a string that makes the test succeed (e.g. "ABCDE1234"), the Like operator is about 4 times faster than the regular expression. Not bad, uh? But the biggest surprise came when I benchmarked the same test based on methods of the System.Char class exclusively:

If teststring.Length = 9 AndAlso teststring.Char(0) = "A"c AndAlso Char.IsDigit(teststring.Char(5)) Then
   AndAlso
Char.IsDigit(teststring.Char(6)) AndAlso Char.IsDigit(teststring.Char(7))
  
AndAlso Char.IsDigit(teststring.Char(8)) Then match = True

Despite of its length, this last test is about five times faster than the Lik operator, and therefore about 20 times faster than the regexes! The gap gets closer if using compiled regexes, but the System.Chars approach is by far the fastest of the lot.

The bottom line: (1) if you write VB code, use the Like operator instead of regexes if the condition isn't too complex, and (2) regardless of the language you work with, if you really want the highest performance, use the methods of the String and Char types, if the search operation isn't too complex.

12/1/2005 10:56:56 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Friday, November 25, 2005

Every now and then I get an email from a reader or a customer, who asks for clarifications on object finalization and disposing. As far as I know, the best article on this topic is this essay by Joe Duffy. It's over 25-page long, covers both .NET 1.1 and 2.0, and includes comments from gurus such as Jeffrey Richter e Chris Brumme. This is easily the definitive article on this topic and I urge you to read it if you haven't already.

The Dispose-Finalize pattern is objectively a complex matter. However, in most cases it can be simplified significantly if you use the following approach: (1) the class with the Dispose/Finalize method should wrap only one single unmanaged resource, and (2) this finalizable class should be private and nested inside another disposable (but not finalizable) type. The outer class is the only class that can use the finalizable class.

This simple trick enables the GC to immediately release all the memory used by the wrapper (disposable) class even in the worst case - that is, if the client code omits to invoke the Dispose method - and simplifies the structure of the type that uses the unmanaged resource. A listing is worth one thousand words, thus here is the C# version of what I mean:

// the class that clients use to work with the unmanaged resource
class WinResource : IDisposable
{
  // private field that creates a wrapper for the unmanaged resource
  private UnmanagedResourceWrapper wrapper = null;
  // this is true if the object has been disposed of
  bool disposed = false;

  public WinResource(string someData)
  {
    // allocate the unmanaged resource here
    wrapper = new UnmanagedResourceWrapper(someData);
  }

  // a public method that clients call to work with the unmanaged resource
  public void DoSomething()
  {
    // throw if the object has been already disposed of
    if ( disposed )
      throw new ObjectDisposedException("");

    // this code can pass the wrapper.Handle value to API calls.
    // ...
  }

  public void Dispose()
  {
    // avoid issues when multiple threads call Dispose at the same time.
    lock ( this )
    {
      // do nothing if already disposed of
      if ( disposed )
        return;
      // dispose of all the disposable objects used by this instance
      // including the one that wraps the unmanaged resource
      // ...
      wrapper.Dispose();
      // remember this object has been disposed of
      disposed = true;
    }
  }
 
  // the nested private class that allocates and release the unmanaged resource
  private sealed class UnmanagedResourceWrapper : IDisposable
  {
    // an invalid handle value, that the wrapper class can use to check
    // whether the handle is valid
    public static readonly IntPtr InvalidHandle = new IntPtr(-1);

    // a public field, but accessible only from inside the WinResource class 
    public IntPtr Handle = InvalidHandle;

    // the constructor takes some data and allocates the unmanaged resource (eg a file)
    public UnmanagedResourceWrapper(string someData)
    {
      // this is just a demo...
      this.Handle = new IntPtr(12345);
    }

    // the Dispose method can be invoked only by WinResource class
    public void Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    // the finalizer
    ~UnmanagedResourceWrapper()
    {
      Dispose(false);
    }

    // This is where the unmanaged resource is actually disposed of.
    // Notice that it takes an argument only for compliance with .NET coding standards
    // but the disposing argument is never used, because in all cases this class
    // can access and release only the single unmanaged resource it wraps.
    private void Dispose(bool disposing)
    {
      // exit now if this object didn't completed its constructor correctly
      if ( this.Handle == InvalidHandle )
        return;
  
      // release the unmanaged resource 
      // eg. CloseHandle(Handle);
      
      // finally, invalidate the handle
      this.Handle = InvalidHandle;
    }
  }
}

Notice that, if the unmanaged resource must interact with other fields, this interaction should be taken care of inside the WinResource class, not in the nested class. The UnmanagedResourceWrapper works only as a wrapper for the handle and shouldn't contain other fields or methods, besides those shown in the above listing. The code in the WinResource class must coordinate all the resources being used, both managed and unmanaged ones, and must release all of them in its Dispose method. But if the client code omits to call the Dispose method, the destructor in the nested class will orderly release the unmanaged resource during the next garbage collection.

Let's see all the advantages of this simplified approach.

  • The requirement that you shouldn't access reference fields from inside the Finalize method is automatically satisfied, because the only field of the UnmanagedResourceWrapper type is a handle (a value type).
  • If the client code omits to invoke the WinResource.Dispose method before the WinResource object goes out of scope, the WinResource object is removed from the heap anyway at the first GC; only the few bytes used by the UnmanagedResourceWrapper object survive in the heap and will be promoted to generation 1 or 2. Therefore this technique is more efficient than writing a single finalizable object that allocates both managed and unmanaged resource.
  • The UnmanagedResourceWrapper class is private and you can't inherit from it, therefore you can mark it as sealed. This means that you never have to worry about the Dispose/Finalize pattern in derived classes - a topic on which tons of digital ink has been spilled. It is possible to inherit from WinResource as you'd do with disposable class, therefore there are no limitations in this respect. (It's exactly like when you inherit from other disposable classes such as FileStream.)
  • The UnmanagedResourceWrapper is private and nested in another type and it isn't possible to achieve a refernence to one of its instances; therefore, a client can't "resurrect" a UnmanagedResourceWrapper object during the finalization step, a technique that is rarely useful and often dangerous. (Even though I show in my Programming Visual Basic .NET book how you can use it to implement an object pool.)
  • The UnmanagedResourceWrapper constructor performs a single "atomic" action; if this action fails, the value of the handle is still qual to InvalidHandle, therefore the code in the Finalize method can detect this special value and do nothing in that case. There are only two cases: either the unmanaged resource has been correctly allocated or an exception prevented it from being created, and you don't have to worry about an object that has been built only partially because of an exception in its constructor.
  • Many other recommendations related to the Dispose/Finalize pattern become void, such as the one that dictates that you should neither write finalizers inside structures nor calling virtual methods from inside the finalizer. In fact, the UnmanagedResourceWrapper class is sealed and has no virtual methods. Nor do you have to worry about versioning issues.
  • Another advantage: the UnmanagedResourceWrapper class is so simple and generic that you can ofter reuse it as-is (or with minor edits) inside other classes, by means of a plain copy-and-paste action. Being a nested class, you don't even need to change its name to avoid name collisions.

I am sure that in some cases this simplified pattern can't be used, though it always worked well in my applications. I believe that it's quite odd that this simplified approach is rarely mentioned in articles and books on this topic.


Technical matters aside, I think that another kind of consideration about the Dispose/Finalize pattern is in order.

In my opinion, it is essential to put a lot of emphasis on the fact that the Dispose/Finalize pattern should be used only when your type invokes unmanaged code that allocates unmanaged resources (including unmanaged memory) and returns an handle that you must use eventually to release the resource. If the unmanaged resource is already wrapped by a .NET object (e.g. a FileStream or a SqlConnection) or a COM object, the .NET class that uses the resource must implement IDisposable, not the finalizer. And you must implement the Finalize method only if your code assigns the handle to a class-level field. If the handle is assigned to a local variable and the unmanaged resource is released before exiting the method - possibly in the Finally section of a Try block - you don't even have to implement the IDisposable interface. One of the few exceptions to this rule is when your managed code allocates unmanaged memory directly, by means of the System.Runtime.InteropServices.Marshal type.

I talked to many developers who believe that, in doubt, they should always implement the Finalize method, just in case. This is a common mistake. Defining a finalizable class without a real reason to do so can hurt performance, because the CLR takes slightly longer to allocate finalizable objects, because it has to register them in the f-reachable queue. And in the worst case - that is if the caller omits to call to the Dispose method - a finalizable object can have even more impact on performance, because it will be promoted to a higher generation without any real reason for such an overhead.

To recap: when do you really need to implement the Finalize method? Thinking of all the commercial apps I worked on in these years, I'd say that I used this pattern no more than 4 or 5 times. For sure, I used it more frequently in books and articles than in the real world. :-)

11/25/2005 10:37:38 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, November 22, 2005
I work with Microsoft Press since 1998 and I wrote as many as 5 books for them (plus 3 more books I am working on right now). Every three months I get a check with my royalties from my US books and the translation rights for versions published elsewhere in the world, but without specifying which languages the books have been translated into.

For some reason I always forgot to ask for a list of the languages my books had been translated into, until a couple of months ago, when by acquisition editor made a search and returned this the following list. Every now and then, readers ask whether the book has been translated to their language, therefore I decided to post the information here.

Programming Microsoft Visual Basic 6 : English, Italian, Japanese, Korean, Spanish, Chinese (simplified, China), Chinese (traditional, Taiwan) + local English-language version in India.

Programming Microsoft Visual Basic .NET: English, Italian, French, Arabic, Japanese, Korean, Spanish, Chinese (simplified), Chinese (traditional) + local English-language versions in China and India.

Applied Microsoft .NET Framework Programming in Microsoft Visual Basic .NET (with Jeffrey Richter): English, Italian, Korean, Chinese (simplified), Chinese (traditional) + local English-language in China.

Programming Microsoft Visual Basic .NET Version 2003: English, Italian.

Practical Guidelines and Best Practices: English, Italiano, Russian + local English-language in China.

I can't help admitting that being translated into as many as ten languages is truly thrilling. :-)

11/22/2005 1:40:29 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, November 21, 2005

I am back from the Windows Professional Conference (Milan, Italy), for which I also server as the chairman for two of the four tracks. The conference touched virtually all the new features of Visual Studio and SQL Server 2005, plus tons of other topics. But that's another story.

When preparing the material for the conference I had to pack all my code samples in a zip file. Even after zipping them, I came up with a 7-8M file, which is a bit too much. So I was about to manually delete all the files that could be recreated by recompiling the projects. It's the same old story, that repeat itself with all conferences, books, articles for magazines and blog posts.

Instead of using the manual approach, this time I decided to write a tiny utility that does the work for me. It took (literally) two minutes to write a console utility that takes the path as an argument and recurses over that directory tree to remove all the folders named "bin" and "obj". If a delete operation fails, a message error is displayed. (This happens when an executable is running and is therefore locked by the operating system.) The code is especially concise thanks to an overload of the Directory.GetDirectories method (added in .NET 2.0) that returns all the directory in a tree.

Imports System.IO

Module Module1
  
Sub Main(ByVal args() As String)
      ' Use current directory if no argument has been specified
     
Dim rootDir As String = Directory.GetCurrentDirectory()
     
If args.Length > 0 Then rootDir = args(0)
     
' Read all the folder names in the specified directory tree
     
Dim dirNames() As String = Directory.GetDirectories(rootDir, "*.*", SearchOption.AllDirectories)
     
Dim errors As Integer = 0

      ' Delete all the BIN and OBJ subdirectories
     
For Each dir As String In dirNames
        
Dim dirName As String = Path.GetFileName(dir).ToLower()
           
If dirName = "bin" OrElse dirName = "obj" Then
              
Try
                 
Console.Write("Deleting {0} ...", dir)
                  Directory.Delete(dir,
True)
                  Console.WriteLine(
"DONE")
              
Catch ex As Exception
                  Console.WriteLine()
                  Console.WriteLine(
" ERROR: {0}", ex.Message)
                  errors += 1
              
End Try
           
End If
        
Next

         Console.WriteLine()
        
If errors = 0 Then
           
Console.WriteLine("All directories were removed successfully")
        
Else
           
Console.WriteLine("{0} directories couldn't be removed")
        
End If
    
End Sub
End
Module

In addition to using this tool from the command line, you can add it to the Tools menu, so that you can quickly delete all the files produced by compiling the current solution, by using this command:

               DELETEBINPATH $(SolutionDir)

where of course DeleteBinPath is the name you used when compiling the utility.

UPDATE: I have posted a new version of this utility in a more recent post.

11/21/2005 9:23:28 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [2]  | 
 Friday, November 18, 2005

Let's consider the following code, which represents a typical situation: you are inside a nested loop and you want to exit both loops when a condition is true:

For i As Integer = 1 To 10
  
Dim exiting As Boolean = False
  
For j As Integer = 1 To 20
     
' If the Evaluate function returns zero you want to exit both loops
     
If Evaluate(i, j) = 0 Then
        
exiting = True
        
Exit For
     
End If
      ' Do something here
   Next
   If exiting Then Exit For
Next

It isn't important to understand what the Evaluate function does, just consider that when this function returns zero you must exit both loops. The above code isn't optimized, because it repeatedly tests the exiting variable. You might optimize the loop by using a Goto statement that points to a label following the second Next keyword, but educated programmers don't use Gotos, right? So, the question is simple: how can you simplify this code and optimize it at the same time by dropping the exiting variable?

The solution is simple, and is based on the fact that Visual Basic supports as many as three different kinds of loops: For, Do, and While. Each kind of loop supports a corresponding Exit keyword (Exit For, Exit Do, and Exit While), thus you can rewrite the code as follows:

Dim i As Integer = 1
Do While i <= 10
  
For j As Integer = 1 To 20
     
If Evaluate(i, j) = 0 Then Exit Do
      ' Do something here
   Next
  
i += 1
Loop

You can use the same technique when you have up to three nested loops.

Incidentally, you can't adopt this technique in C#, because its break statement doesn't have the same "semantics power" of the Exit keyword in VB.

11/18/2005 9:09:12 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, November 16, 2005

I am reviewing the chapter devoted to the My Namespace and I am adding a few details that I overlooked in the first draft, such as the creation of custom setting providers (to save settings on a medium other than the configuration file, e.g. a database) and the ability to bind a control's property to a user sertting. Custom setting providers are relatively complex and are of interest for a relatively small number of users, whereas setting binding is a simpler topic that will surely draw the attention of any developer working with Windows Forms applications.

I found many articles and posts on the My.Settings object (VB) and Settings object (C#), but most of them omit to emphasize the ability to bind a user setting to a form property, such as the Size and Location properties. This feature enables you - among the many things - to restore the size and position of a form from a previous session. The .NET infrastructure automatically assigns these properties when the form is loaded and save them when the form is resized or moved.

Figure 1. Visual Studio 2005 enables you to define user-level and application-level settings

Figure 2. How to bind a property to a user-level setting.

The great thing of this technique is that you don't need to write a single line of code. In fact, you just need to define one or more settings in the Settings page of the My Project designer (Visual Basic) or Properties designer (C#), for example the MainFormLocation and MainFormSize settings (see Figure 1). It is crucial that these settings are defined as user settings, because application-level settings are read-only. Next, you can select the form, switch to the Properties window, open the (Application Settings) section, click on the arrow near the ClientSize and Location properties, and select the user setting you want to bind the property to. If you haven't created the user setting yet, you can do it now by clicking on the New element. (See Figure 2.)

As I already noted, the noteworthy detail is that these settings are automatically updated when the end user moves or resizes the form. You can bind other properties, for example Text, BackColor, etc. If you perform this action for all the forms in the application, you can implement a simple yet powerful persistance mechanism for all user's preferences, again without writing code!

Obviously, you can extend this mechanism to properties of individual controls. Not all properties can notify to the world that they have been modified, though. More precisely, the control that exposes the property must implement the IBindableComponent interface and must expose an event named XxxxChanged for each property, or it must implement the INotifyPropertyChanged interface (new in .NET 2.0). Most Windows Forms controls, but not all of them, implement these interfaces. For example, the ToolStripItem control doesn't implement it. In this case, the property is assigned correctly when the form loads, but you must update the corresponding user setting via code.

 

11/16/2005 8:57:59 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [3]  | 
 Sunday, November 13, 2005

Visual Studio 2005 comes with dozens of ready-to-use code snippets. You might argue on the usefulness of some of them, but for sure many of them are really well-conceived. For example, the prop expansion that creates public C# properties is a real time saver.

The Code Snippet Manager dialog box (in the Tools menu) enables you to inspect all the installed snippets, one by one, but doesn't offer the option to export a list of all the snippets, therefore you have to browse them one by one to take notice of their name, purpose, and keyboard shortcut. While I was working on chapter 4 of Programming Microsoft Visual Basic 5, I wrote this little throw-away program which decodes the snippet index and list them on a console window. Of course you can redirect the output to a file to have a document that you can use as a reference.

The program takes a parameter equal to the path of the SnippetIndex.xml (VB) or SnippetsIndex.xml (C#) file that contains the snippet index. (Oddly, this file has a slightly different name in the two languages.) If you run it without passing any argument, it uses the path of the VB snippet index in a default Visual Studio installation. A comment in the listing explains how you can use the default index for C# instead.

The output of this code is quite terse - just snippet names and shortcuts, grouped in categories - but you can easily modify the source code to extract and display more attributes.

Imports System.IO
Imports System.Xml
Imports System.Text.RegularExpressions

Module Module1
  
Dim snippetsPath As String
  
Dim catNames As New Dictionary(Of String, String)

   Sub Main(ByVal args() As String)
     
' If no argument has been provided, use default path for snippets.
     
If args.Length = 0 Then
        
args = New String() {"C:\Program Files\Microsoft Visual Studio 8\Vb\Snippets\1033\SnippetIndex.xml"}
        
' Uncomment next line to list C# snippets
        
' args = New String() {"C:\Program Files\Microsoft Visual Studio 8\VC#\Snippets\1033\SnippetsIndex.xml"}
     
End If

      Dim snippetsFile As String = args(0)
      snippetsPath = Path.GetDirectoryName(snippetsFile)
     
' Load the snippet index file.
     
Dim xmlIndex As New XmlDocument()
      xmlIndex.Load(snippetsFile)
     
' We need two passes, because dirs and subdirs use a different XML element.
     
ParseSnippetIndex(xmlIndex, "//SnippetDir")
      ParseSnippetIndex(xmlIndex,
"//SnippetSubDir")
     
' Iterate over all the directories in the main snippet directory.
     
For Each dir As String In Directory.GetDirectories(snippetsPath)
         ParseSnippetFolder(dir,
"")
     
Next
   End Sub

   Sub ParseSnippetIndex(ByVal xmlIndex As XmlDocument, ByVal searchKey As String)
     
' Create the correspondence between relative paths and localized categories
     
For Each xmlEl As XmlElement In xmlIndex.SelectNodes(searchKey)
        
Dim elPath As XmlElement = DirectCast(xmlEl.SelectSingleNode("DirPath"), XmlElement)
        
Dim elName As XmlElement = DirectCast(xmlEl.SelectSingleNode("LocalizedName"), XmlElement)
         catNames.Add(elPath.InnerText, elName.InnerText)
     
Next
   End Sub

   Sub ParseSnippetFolder(ByVal dir As String, ByVal parentCategory As String)
     
' Retrieve the relative name of this subdirectory.
     
Dim relPath As String = dir.Substring(snippetsPath.Length)
     
' The default name for this category
     
Dim categoryName As String = parentCategory & Path.GetFileNameWithoutExtension(dir)
     
' Search this relative path in the snippet index.
     
Dim searchPath As String = "%InstallRoot%\Vb\Snippets\%LCID%" + relPath + "\"
     
If catNames.ContainsKey(searchPath) Then
        
' If found, use the localized category as appears in the index file
        
categoryName = parentCategory & catNames(searchPath)
     
End If
     
Console.WriteLine(categoryName.ToUpper())
     
' Parse individual snippets in this directory.
     
For Each file As String In Directory.GetFiles(dir, "*.snippet")
         ParseSnippetFile(file)
     
Next
     
' Parse all sub-categories
     
For Each subdir As String In Directory.GetDirectories(dir)
         ParseSnippetFolder(subdir, categoryName &
" / ")
     
Next
   End Sub

   Dim reTitle As New Regex("<Title>(.+?)</Title>")
   Dim reShortcut As New Regex("<Shortcut>(.+?)</Shortcut>")

   Sub ParseSnippetFile(ByVal snippetFile As String)
     
Dim text As String = File.ReadAllText(snippetFile)
     
' We use regexes to extract information for individual snippet files.
     
Dim maTitle As Match = reTitle.Match(text)
     
Dim maShortcut As Match = reShortcut.Match(text)
     
Dim title As String = maTitle.Groups(1).Value
     
Dim shortcut As String = maShortcut.Groups(1).Value
      Console.WriteLine(
" {0} [{1}]", title, shortcut)
  
End Sub
End
Module

11/13/2005 7:47:53 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [1]  | 
 Friday, November 11, 2005

I find it quite ironical that many developers spend hours to debate which language is the most efficient or productive, and yet forget to learn how to use the tool with which they spend most of their time: the Visual Studio IDE.

The best way to increase productivity with Visual Studio is to write macros that automate repetitive tasks. There are many commercial and freeware add-ins on the market, but I rarely find one that does exactly what I need. In cases like this I just write a macro, either from scratch or starting with a recorded macro that captures the actions that I want to repeat.

For example, I found that I typically prototype my classes with Public fields, but then I convert them to properties when I convert the prototype to the "real" code. The conversion process takes me a lot of time. To see what I mean, I typically start with a simple variable such as

' The name of the element
Public Name As String = "Francesco"

and I convert it into something like this:

' The name of the element
Private
m_Name As String = "Francesco"

Public Property Name() As String
   Get
      Return m_Name 
   End Get
   Set(ByVal Value As String)
      m_Name = Value
   End Set
End Property

At last, some months ago I decided to write a macro that automates this conversion. It took me about 30 minutes, but in these months it saved me hours. Here it is:

Imports EnvDTE
Imports System.Text.RegularExpressions

Public Module CodeArchitectsMacros
  
Dim repPattern As String
   Dim repPatternReadOnly As String

   Sub ConvertVariables()
     
' Determine current language by looking at the extension of the current document.
     
Dim doc As
Document = DTE.ActiveDocument
     
If doc Is Nothing Then Exit
Sub
     
Dim docName As String
= doc.Name.ToLower()

      ' Read all the text lines touched by the selection.
     
Dim sel As TextSelection = CType
(DTE.ActiveDocument.Selection, TextSelection)
     
Dim ed1 As
EditPoint = sel.AnchorPoint.CreateEditPoint()
     
ed1.EndOfLine() : ed1.StartOfLine() : ed1.StartOfLine()
     
Dim ed2 As
EditPoint = sel.BottomPoint.CreateEditPoint()
      ed2.EndOfLine()
     
Dim text As String
= ed1.GetText(ed2)

      ' The find and replacement pattern depend on the current language.
     
Dim findPattern As
String
     
If docName.EndsWith(".vb")
Then
         findPattern = "(?<indent>[\t ]+)Public\s+(?<static>Shared\s+)?(?<readonly>ReadOnly\s+)?" _
            & "(?<name>\w+)\s+As\s+(?<type>\S+)(?<init>.*?)\n"
         ' {0}=property name, {1}=property type, {2}=static keyword, {3} initvalue,
         ' {4}=CR-LF, {5}=Tab, {6}=indent
        
repPattern = "{6}Private {2}m_{0} As {1}{3}{4}" _
            & "{6}Public {2}Property {0}() As {1}{4}" _
            & "{6}{5}Get{4}" _
            & "{6}{5}{5}Return m_{0}{4}" _
            & "{6}{5}End Get{4}" _
            & "{6}{5}Set(ByVal Value As {1}){4}" _
            & "{6}{5}{5}m_{0} = Value{4}" _
            & "{6}{5}End Set{4}" _
            & "{6}End Property{4}{4}"
         repPatternReadOnly = "{6}Private {2}ReadOnly m_{0} As {1}{3}{4}" _
            & "{6}Public ReadOnly {2}Property {0}() As {1}{4}" _
            & "{6}{5}Get{4}" _
            
& "{6}{5}{5}Return m_{0}{4}" _
            & "{6}{5}End Get{4}" _
            & "{6}End Property{4}{4}"

     
ElseIf docName.EndsWith(".cs")
Then
         ' Notice the (?.*;) element is needed to ensure that public fields are matched,
         ' but public properties aren't
         
findPattern = "(?<indent>[\t ]+)public\s+(?<static>static\s+)?(?<readonly>readonly\s+)?" _
            "(?<type>\S+)\s+(?<name>\w+)(?=.*;)(?<init>.*?)\n"

        
' {0}=property name, {1}=property type, {2}=static keyword, {3} initvalue,
         ' {4}=CR-LF, {5}=Tab, {6}=indent
        
repPattern = "{6}private {2}{1} m_{0}{3}{4}" _
            & "{6}public {2}{1} {0}{4}" _
            & "{6}{{{4}" _
           
& "{6}{5}get {{ return m_{0}; }}{4}" _
            & "{6}{5}set {{ m_{0} = value; }}{4}" _
            & "{6}}}{4}{4}"
         repPatternReadOnly = "{6}private {2}readonly {1} m_{0}{3}{4}" _
            & "{6}public {2}{1} {0}{4}" _
            & "{6}{{{4}" _
            & "{6}{5}get {{ return m_{0}; }}{4}" _
            & "{6}}}{4}{4}"

     
End If

     ' Replace the text. Add a trailing CR-LF but remove it later.
    
Dim replaceText As String = Regex.Replace(text + ControlChars.CrLf, findPattern, _
       
AddressOf ReplaceWithProperty)
     ed1.ReplaceText(ed2, replaceText.Substring(0, replaceText.Length - 2), 0)

   End Sub

   ' Private callback function for the Replace method
  
Private
Function ReplaceWithProperty(ByVal m As Match) As
String
      Dim pattern As String = repPattern
     
If m.Groups("readonly").Length > 0 Then pattern = repPatternReadOnly
     
Return String.Format(pattern, m.Groups("name").Value, m.Groups("type").Value, _
         m.Groups("static").Value, m.Groups("init").Value, ControlChars.CrLf, _
         ControlChars.Tab, m.Groups("indent").Value)

   End
Function

End Module

Thanks to regular expressions, and in spite of the low amount of code it contains, this macro works both in VB and C#, it enables you to convert multiple fields in one shot, it preserves the field's initial value and even its static/Shared and Readonly attributes, and it also preserves any statement between variable declarations. In practice, therefore, you can just select the source code of an entire class and convert all its public fields into properties, with just a mouse click! :-)

For each property, the macro creates a variable named m_PropertyName; obviously you can use your favorite naming convention by editing the statement that assigns regPattern. C# developers can edit the code to generate multi-lined get/set blocks. (I prefer to have more compact blocks.)

11/11/2005 6:19:09 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [10]  | 
 Thursday, November 10, 2005

The Array class has been expanded with many generic methods. For example, consider the following code:

int[] intArray = new int[] { 12, 34, 56, 78, 90 };
// convert each element to hex
string[] strArray = new string[intArray.Length];
for (int i = 0; i < intArray.Length; i++)
{
   strArray[i] = intArray[i].ToString(
"X");
}
// display the result in the Console window
foreach (string s in strArray)
{
  
Console.WriteLine(s);
}

Using the Array.ConvertAll method and an anonymous method you can simplify the conversion loop as follows:

string[] strArray = Array.ConvertAll<int, string>(intArray, delegate(int n)
                      
 { return n.ToString("X"); });

Surprisingly, however, you can simplify this code even further and even render it with VB 2005 (which doesn't support anonymous methods). The trick is to find a static method in the .NET Framework that takes a number and returns the argument's hex value. Strictly speaking, the .NET Framework doesn't expose a type with such a method, but you can use the Hex method of the Microsoft.VisualBasic.Conversion type:

// this code requires a reference to the Microsoft.VisualBasic.dll
string[] strArray = Array.ConvertAll<int, string>(intArray, Microsoft.VisualBasic.Conversion.Hex);

' This the VB version
Dim
strArray As String() = Array.ConvertAll(Of Integer, String)(intArray, AddressOf Hex)

The Visual Basic library exposes a few other methods that you can use in this fashion, for example UCase, LCase, Trim, LTrim, RTrim, Int, Val, Asc, Chr, Len. You can find other useful methods everywhere in the .NET Framework, for example the Convert class.

Likewise, you can replace the loop that displays the results to the console window with a simpler Array.ForEach method

// C#
Array.ForEach<string>(strArray, Console.WriteLine);

' VB
Array.ForEach(Of String)(strArray, AddressOf Console.WriteLine)

11/10/2005 6:04:05 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [4]  | 
 Monday, November 07, 2005

Here's a non-orthodox but quite effective technique I sometimes use to detect and avoid recursive calls to a method. You typically detect recursive calls by defining a boolean class-level field and testing it on entry to a method. This technique is often used in event handlers, for example in TextChanged handlers that modify the Text property of a control and that would therefore trigger an endless recursion:

Dim insideTextChanged As Boolean

Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
   ' Exit if this is a recursive call.
  
If insideTextChanged Then Exit Sub
  
' Forbid recursive calls from now on.
   insideTextChanged = True
  
' ...
  
TextBox1.Text = TextBox1.Text & " "
   ' Permit recursive calls.
  
insideTextChanged = False
End Sub

This approach works well, but it requires a lot of code and forces you to define a distinct boolean field for each event handler. If you have many handlers, it quickly becomes a nuisance. In addition, if there is any chance that the method throws an exception, you must wrap all the code in a try block,so that you can reset the insideTextChanged to false in the finally section. Wouldn't it great if you could use a method that allows you to test if you are inside a recursive call? I am thinking of something like this:

Private Sub TextBox1_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TextBox1.TextChanged
   ' Exit if this is a recursive call.
  
If IsRecursive() Then Exit Sub
   ' ...
  
TextBox1.Text = TextBox1.Text & " "
End Sub

Here's how you can implement the IsRecursive method:

<System.Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
Public Shared Function IsRecursive() As Boolean
  
Dim st As New StackTrace
   ' Check whether any method in the call stack is the same as the immediate caller.
  
For n As Integer = 2 To st.FrameCount - 1
      If st.GetFrame(1).GetMethod() Is st.GetFrame(n).GetMethod() Then Return True
  
Next
  
Return False
End Function

Here's the C# version:

[System.Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)]
public static bool IsRecursive() 
{
  
StackTrace st = new StackTrace();
   // Check whether any method in the call stack is the same as the immediate caller.
  
for ( int n= 2; n < st.FrameCount; n++ )
   {
      if ( st.GetFrame(1).GetMethod() == st.GetFrame(n).GetMethod()
        
return true;
   
}
  
return false;
}

The IsRecursive method compares the immediate caller - that is, st.GetFrame(1).GetMethod() - with all the other methods on the call stack and returns True if it finds a match. It is essential that the IsRecursive method is decorated with the MethodImpl attribute, to ensure that the JIT compiler inlines it in its caller's body. In .NET 1.1 this should never happen, because the JIT compiler never inlines methods that contain loops, but I haven't checked under .NET 2.0 and obviously I can't make promises about future versions, therefore this attribute is your best defence.

11/7/2005 8:38:20 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, November 06, 2005

C# 2.0 has Surround With command that enables you to wrap the selected code inside a if, for, foreach, while, #if (and a few mode) blocks. Some of the options are virtually useless - for example, I'd never wrap a piece of code in a class, interface, or enum block - but all in all it's a very handy command. Actually, it is so convenient that I decided to create a set of Visual Studio macros that add the same functionality to Visual Basic (both 2003 and 2005 editions) and C# 2003.

To install and use the macros listed below, add the following module to the Macro IDE, go back to Visual Studio, select a piece of code, open the WrappingMacros element in the Macro Explorer window, and double-click the macro you want to apply. In some cases, after applying the macro you'll also need to edit the generated code, for example to insert a condition in the If statement or the name of the #region you've created. Also, if you are working with C# you should manually reformat the selected code (by typing Ctrl+K, Ctrl+F), because for some reason the Edit.FormatSelection command works only in Visual Basic.

Even better, you should associate the macros you like most with a keyboard shortcut, so that you can apply the macro without opening the Macro Explorer window. If you need to list which shortcuts are available, have a look at yesterday's post.

Imports System
Imports EnvDTE

Public Module WrappinglMacros

   ' --------------------------------------------------------------------
   ' Wrap the selected code inside IF, TRY, etc.
   ' --------------------------------------------------------------------

   Public Sub WrapIf()
      WrapCode("WrapIf", "If True Then\n$sel$End If\n", "if ( true )\n{\n\t$sel$}\n")
   End Sub

   Public Sub WrapIfElse()
      WrapCode("WrapIfElse", "If True Then\n$sel$Else\n\nEnd If\n", "if ( true )\n{\n\t$sel$}\nelse\n{\n}\n")
   End Sub

   Public Sub WrapTryCatch()
      WrapCode("WrapTryCatch", "Try\n$sel$Catch ex As Exception\n\nEnd Try", _
         "try\n{\n$sel$}\ncatch (Exception ex)\n{\n}\n")
   End Sub

   Public Sub WrapTryFinally()
      WrapCode("WrapTryFinally", "Try\n$sel$Finally\n\nEnd Try", "try\n{\n$sel$}\nfinally\n{\n}\n")
   End Sub

   Public Sub WrapTryCatchFinally()
      WrapCode("WrapTryCatchFinally", "Try\n$sel$Catch ex As Exception\n\nFinally\n\nEnd Try", _
         "try\n{\n$sel$}\ncatch (Exception ex)\n{\n}\nfinally\n{\n}\n")
   End Sub

   Public Sub WrapRegion()
      WrapCode("WrapRegion", "#Region ""RegionName""\n\n$sel$\n#End Region", _
         "#region RegionaName\n\n$sel$\n#endregion")
   End Sub

   Public Sub WrapSharpIf()
      WrapCode("WrapSharpIf", "#IF True Then\n\n$sel$\n#End If", "#if true\n\n$sel$\n#endif")
   End Sub

   Public Sub WrapFor()
      WrapCode("WrapFor", "For index As Integer = startIndex To endIndex\n$sel$Next", _
         "for (int index = startIndex; i <= endIndex; index++)\n{\n$sel$}\n")
   End Sub

   Public Sub WrapForEach()
      WrapCode("WrapForEach", "For Each obj As Object In collection\n$sel$Next", _
         "foreach (object obj in collection)\n{\n$sel$}\n")
   End Sub

   Public Sub WrapWhile()
      WrapCode("WrapWhile", "Do While True\n$sel$Loop", "while (true)\n{\n$sel$}\n")
   End Sub

   Public Sub WrapDoWhile()
      WrapCode("WrapDoWhile", "Do\n$sel$Loop While True", "do\n{\n$sel$} while ( true );\n")
   End Sub

   Public Sub WrapNamespace()
      WrapCode("WrapNamespace", "Namespace NamespaceName\n$sel$End Namespace", _
         "namespace NamespaceName\n{\n$sel$} // end of namespace")
   End Sub

   Public Sub WrapSelect()
      WrapCode("WrapSelect", "Select Case expression\nCase 0\n$sel$Case 1\nCase Else\nEnd Select\n", _
        
"switch ( expression )\n{\n\tcase 0:\n\t\tbreak;\n\tcase 1:\n\t\tbreak;\n\tdefault:\n\t\tbreak;\n}\n")
   End Sub

   Public Sub WrapSyncLock()
      WrapCode("WrapIf", "SyncLock lockObject\n$sel$End SyncLock\n", "lock ( lockObject )\n{\n\t$sel$}\n")
   End Sub

   ' Helper method that replaces the selection with the specified templated text.
   ' The template can include $sel$ (the selected code) and escape sequences such as \r\n, \t
  
Private Sub WrapCode(ByVal cmdName As String, ByVal vbTemplate As String, ByVal csTemplate As String)
     
' Determine the current language by looking at the extension of the current document.
     
Dim doc As Document = DTE.ActiveDocument
     
If doc Is Nothing Then Exit Sub
     
Dim docName As String = doc.Name.ToLower()
     
Dim sel As TextSelection = DirectCast(DTE.ActiveDocument.Selection, TextSelection)
     
If sel Is Nothing Then Exit Sub

      ' Open an undo context.
     
DTE.UndoContext.Open(cmdName)
     
' Retrieve the selected text, append a newline if necessary.
     
Dim selText As String = sel.Text
     
If Not selText.EndsWith(ControlChars.NewLine) Then selText &= ControlChars.NewLine

     
' Wrap the selected text, using either the VB or the C# command
     
Dim template As String
     
If docName.EndsWith(".vb") Then
        
template = vbTemplate
     
ElseIf docName.EndsWith(".cs") Then
        
template = csTemplate
     
End If

      ' Replace CR-LF, tabs, and the selected text
     
Dim newText As String = Regex.Unescape(template).Replace("$sel$", selText)
     
' Reselect the text just added and format it. (Doesn't work in C#.)
     
Dim ep As EditPoint = sel.TopPoint.CreateEditPoint()
      sel.Text = newText
      sel.MoveToPoint(ep,
True)
      DTE.ExecuteCommand("Edit.FormatSelection")
     
' Close the undo context.
     
DTE.UndoContext.Close()
   End Sub

End Module

11/6/2005 8:20:12 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Saturday, November 05, 2005

When you create a macro and want to associate it with a shortcut key you face the problem of determining which shortcuts are available. At times this can become a time-consuming job, because Visual Studio has taken so many shortcuts for itself. More in general, how do you get the list of all the Visual Studio commands and their shortcuts, as defined in a given keyboard configuration?

The most obvious answer is "read the documentation, dude!", however the docs can't list the shortcuts that have been modified or added after installing Visual Studio. Microsoft provides the Keybindings Table Add-in, an add-in written in C++ that lists all key bindings (that is, the shortcuts associated with each command). But why should you install an add-in if you can achieve the same result with a macro that contains just a few lines of code? Here's a macro that creates a text file in the C:\ folder, with all the information you need:

Public Sub ShowKeyboardBindings()
   Dim sw As New System.IO.StreamWriter("c:\keybindings.txt")
   For Each cmd As Command In DTE.Commands
      For Each o As Object In cmd.Bindings
         sw.WriteLine(cmd.Name & " - " & o.ToString())
      Next
   Next
   sw.Close()
End Sub

The advantage of using a macro instead of the shrik-wrapped add-in is that you can modify its source code to display information in any format you like. For example, you might sort the result by the shortcut, so that you can immediately see which shortcuts are used and which are available. Or you can list only the commands that are available inside a given window, such as the code editor or the form designer.

If you aren't familiar with macros, here's how to proceed. Type Alt+F11 to bring up the Macro IDE, open a module under MyMacros (e.g. Module1) by clicking in the Project Explorer, then paste the previous code and type Ctrl+S to save. Close the Macro IDE and go back to Visual Studio, type Alt+F8 to display the Macro Explorer window, expand the MyMacros-Module1 node and double-click on ShowKeyboardBinding to run the macro. Done!

11/5/2005 8:17:15 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Friday, November 04, 2005

Have a look at this simple Visual Basic code snippet:

' The version that does NOT cache the value type in a reference variable.
Dim start As Date = Now
For i As Integer = 1 To
1000
  
For j = 1 As Integer To
100000
      GetObject(i, j)
  
Next
Next
Console.WriteLine("Version 1: "
& Now.Subtract(start).ToString)
GC.Collect() : GC.WaitForPendingFinalizers()

' The version that caches the value type in a reference variable.
start = Now
For i = 1 As Integer To
1000
  
' Cache the value type in an Object variable.
  
Dim o As Object
= i
  
For j As Integer = 1  To
100000
      GetObject(o, j)
  
Next
Next
Console.WriteLine("Version 2: " & Now.Subtract(start).ToString)

GetObject is a very simple routine, that takes two objects and therefore causes a box operation if they are value types:

Private Function GetObject(ByVal o As Object, ByVal o2 As Object) As Object
  
Return
o
End Function

As you can read in comments, the second portion caches the boxed version of the i variable in an Object variable, because this value doesn't change inside the innermost loop. You'd expect that this second version would run faster, even if by a little, and in fact this is what happens with Visual Basic .NET 2003. However, if you try this code with VB 2005 you'll be surprised to see that - as counterintuitive as it sounds - the version that caches the boxed value is 30-40% slower!

You need ILDASM to understand what happens behind the scenes. Visual Basic calls the GetObjectValue static method of the RuntimeHelpers type (in the System.Runtime.CompilerServices namespace) before passing an object variable to an object argument, and this extra call explains the overhead just observed. The weird thing is that this extra call is generated by the VB2003 compiler as well, however it doesn't nullify our manual optimization based on the cached variable. I am doing the benchmark with the RTM version, therefore this overhead is real (in other words, it isn't caused by pieces of the CLR compiled in debug mode), therefore I can only conclude that the 2.0 version of the GetObjectValue method is less efficient than the 1.1 version.

This is what the GetObjectValue method does. (Thanks to Adrian Florea, who found this note in Rotor's source code.)

GetObjectValue is intended to allow value classes to be manipulated as Object but have aliasing behavior of a value class. The intent is that you would use this function just before an assignment to a variable of type Object. If the value being assigned is a mutable value class, then a shallow copy is returned (because value classes have copy semantics), but otherwise the object itself is returned.

Note: VB calls this method when they're about to assign to an Object or pass it as a parameter. The goal is to make sure that boxed value types work identical to unboxed value types - ie, they get cloned when you pass them around, and are always passed by value. Of course, reference types are not cloned."

11/4/2005 7:49:08 AM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Wednesday, November 02, 2005

Every now and then I discover a Visual Studio shortcut that doesn't correspond to any menu command. Not all these under-documented shortcuts are truly useful, but a few of them can help you accellerate common editing operations and save a fraction of a second. Adding one fraction of a second today and tomorrow, at the end of your programming career you will save two or three entire days. Maybe in those days it will be raining (quoting Woody Allen), but that's a different story :-) Here are some of my discoveries:

Ctrl+L cuts the current line into the Clipboard
CStrl+Shift+L deletes the current line without copying it in the Clipboard.
Ctrl+C, if no text is currently selected, it copies the current line in the Clipboard.
Ctrl+Enter creates an empty line above the current line.
Ctrl+Shift+Enter creates an empty line below the current line.

Ctrl+Shift+T swaps the current word with the word on its right (word transpose).
Ctrl+Alt+T swaps the current line with the next line (line transpose) thus you can easily move a line elsewhere in the listing

Ctrl+F3 finds the next occurrence of the searched text.
Ctrl+Shift+F3 finds the previous occurrence of the searched text.

Ctrl+F10 invokes the Run to Cursor command in debug mode.
Ctrl+Shift+F10 invokes the Set Next Statement command in debug mode.
Ctrl+* (on the numeric keypad) invokes the Show Next Statement command.

Ctrl+PgUp e Ctrl+PgDn move to the previous and next toolwindow among those that are usually hosted near the right border of the IDE, enabling you to cycle among the Solution Explorer, Properties window, Class View window, etc.
Alt+Shift+F6 e Alt+F6 move to the previous and next window among those hosted in the bottommost panel, enabling you to cycle among the Immediate, Command, Task List, and Threads window.
Shift+Esc closes a toolwindow.

All these shortcuts are active if youselect the Visual Studio's default keyboard configuration. If you enabled a different keyboard configuration (e.g. Visual Basic), some of these shortcuts won't work. You can modify the keyboard configuration in the Keyboard page of the Tools-Options dialog box.

11/2/2005 5:47:32 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, November 01, 2005

I am reviewing the chapter on execution flow in Visual Basic 2005, where I cover recursion - among the many things. In most programming books I've read, recusion is explained with the "classic" factorial example (which can be implemented more efficiently with a simple For loop) or as a means to visit tree structures. It looks like recursion isn't useful in the "average" business application, which of course isn't the case. As most programming techniques, it's mostly a matter of knowing when and where to exploit it.

Here's an example of recursion that you might find quite useful: a method that converts an integer into its textual representation, e.g. 1234 into "One Thousand Two Hundreds Thirty Four", taken from my forthcoming Microsoft Press book Programming Microsoft Visual Basic 2005.

Public Shared Function NumberToText(ByVal n As Integer) As String
  
Select Case n
     
Case Is < 0
        
Return "Minus " & NumberToText(-n)
     
Case 0
        
Return ""
     
Case 1 To 19
        
Dim arr() As String = {"One", "Two", "Three", "Four", "Five", "Six", _
            "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve", "Thirteen", _
            "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"}
        
Return arr(n - 1) & " "
     
Case 20 To 99
        
Dim arr() As String = {"Twenty", "Thirty", "Forty", "Fifty", "Sixty", _
            "Seventy", "Eighty", "Ninety"}
        
Return arr(n \ 10 - 2) & " " & NumberToText(n Mod 10)
     
Case 100 To 199
        
Return "One Hundred " & NumberToText(n Mod 100)
     
Case 200 To 999
        
Return NumberToText(n \ 100) & "Hundreds " & NumberToText(n Mod 100)
     
Case 1000 To 1999
        
Return "One Thousand " & NumberToText(n Mod 1000)
     
Case 2000 To 999999
        
Return NumberToText(n \ 1000) & "Thousands " & NumberToText(n Mod 1000)
     
Case 1000000 To 1999999
        
Return "One Million " & NumberToText(n Mod 1000000)
     
Case 1000000 To 999999999
        
Return NumberToText(n \ 1000000) & "Millions " & NumberToText(n Mod 1000000)
     
Case 1000000000 To 1999999999
        
Return "One Billion " & NumberToText(n Mod 1000000000)
     
Case Else
        
Return NumberToText(n \ 1000000000) & "Billions " _
            & NumberToText(n
Mod 1000000000)
   End Select
End Function

Here's the version for curly braces' lovers. C# switch keyword doesn't support ranges, thus I had to change the code to use a series of elseif blocks:

public static string NumberToText( int n)
{
  
if ( n < 0 )
     
return "Minus " + NumberToText(-n);
  
else if ( n == 0 )
     
return "";
  
else if ( n <= 19 )
     
return new string[] {"One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", 
         "Nine", "Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", 
         "Seventeen", "Eighteen", "Nineteen"}[n-1] + " ";
  
else if ( n <= 99 )
     
return new string[] {"Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", 
         "Eighty", "Ninety"}[n / 10 - 2] + " " + NumberToText(n % 10);
  
else if ( n <= 199 )
     
return "One Hundred " + NumberToText(n % 100);
  
else if ( n <= 999 )
     
return NumberToText(n / 100) + "Hundreds " + NumberToText(n % 100);
  
else if ( n <= 1999 )
     
return "One Thousand " + NumberToText(n % 1000);
  
else if ( n <= 999999 )
     
return NumberToText(n / 1000) + "Thousands " + NumberToText(n % 1000);
  
else if ( n <= 1999999 )
     
return "One Million " + NumberToText(n % 1000000);
  
else if ( n <= 999999999)
     
return NumberToText(n / 1000000) + "Millions " + NumberToText(n % 1000000);
  
else if ( n <= 1999999999 )
     
return "One Billion " + NumberToText(n % 1000000000);
  
else 
     
return NumberToText(n / 1000000000) + "Billions " + NumberToText(n % 1000000000);
}

These methods are much simpler than any similar code I've found on the Internet, thanks to recursion. I really love OOP, generics, attributes, regular expressions, and other advanced language features, but I also like to reming that you can often write elegant, compact, and efficient code just leveraging the features that mainstream languages have offered for decades.

11/1/2005 5:32:09 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [3]  | 
 Monday, October 31, 2005

VB.NET and C# compilers manage string constants in a rather smart way: all strings with same value are stored in a common area known as string intern pool. The following code snippet shows this compiler feature in action:

' VB.NET
Dim s1 As String = "ABCDE"
Dim s2 As String = "ABC" & "DE"
' Prove that s1 and s2 point to the same element in the intern pool
Console.WriteLine(s1 Is s2) ' => True

// C#
string s1 = "ABCDE";
string s2 = "ABC" + "DE";
// Prove that s1 and s2 point to the same element in the intern pool
Console.WriteLine(String.ReferenceEquals(s1, s2)); // => True

This optimization technique doesn't really have any impact on the amount of memory used by most client applications, but it makes a difference if used inside types that are instantiated thousand times, as it often happens in server applications. The problem is, this optimization is applied only to string constants, not to strings built at runtime:

' VB.NET ...continuing previous example...
Dim s3 As String = "ABC"
s3 &= "DE"
' s1 and s3 contain the same value but point to a different string
Console.WriteLine(s1 = s3) ' => True
Console.WriteLine(s1 Is s3) ' => False

// C# ... continuing previous example...
string s3 "ABC";
s3 += "DE";
Console.WriteLine(s1 == s3) // => True
Console.WriteLine(String.ReferenceEquals(s1, s3) // => False

Now, let's suppose you have a component in the data tier and this component contains the the connection string for the database. This connection string is read from somewhere - typically the configuration file - when it's time to open the connection, therefore the compiler can't store the string in the intern pool. If this component is instantiated N times, there will be N copies of the same string in memory, which clearly is a waste if the string is long and N is high. There are two ways to avoid this waste, depending on how the connection string can vary.

If the connection string is guaranteed to be the same for all the instances, then you can store it in a static variable (a Shared variable in VB), so that the string is shared among all the instances of the component. This is the simplest case and I assume you know how to implement it, so let's move to the more interesting situation.

If the connection string can vary - for example, if the data component can connect to two or more different databases or if the connection string can use different login information - you can't store it in a static field. In this case you can resort to a technique based on the String.Intern method. This method receives a string argument and searches the argument in the intern pool: if the search is successful, the method returns a pointer to the existing string in the pool; if the search fails, the method inserts the string in the pool and returns a pointer to the element just added. Here's how you might implement the ConnectionString property in the hypothetical data component to better leverage the intern pool:

' VB.NET
Dim m_ConnectionString As String

Property ConnectionString() As String
   Get
      Return m_ConnectionString
   End Get
   Set(ByVal Value As String)
      m_ConnectionString = String.Intern(Value)
   End Set
End Property
 

// C#
private string m_ConnectionString;

public string ConnectionString
{
  get { return m_ConnectionString; }
  set { m_ConnectionString = String.Intern(value);}
}

The first time a given value is assigned to the ConnectionString property, the search in the pool fails, the String.Intern method adds the string in the pool and returns a pointer to the new pool element. If the same connection string is eventualy assigned to a different instance of the data component, the String.Intern pool returns a pointer to the element already in the pool and doesn't create any duplicate. The total amount of memory that the application uses is reduced and so is the number of garbage collections that occur during the application's lifetime.

10/31/2005 6:14:23 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [0]  | 
 Sunday, October 30, 2005

I often need to paste a text fragment as a comment in my source code. Unfortunately I can't simply paste the text and then use the Edit-Comment Selectio command. because the Visual Studio editor - at least when working with VB.NET - tries to interpret the pasted text as code and it ruins its formatting, adds or deletes characters, and so forth. In addition to this problem, when I am preparing samples for my books I need to revise all CR-LFs in the text, to wrap longer lines, because Microsoft Press standards mandate that lines aren't longer than 92 characters. All in all, it's a real nuisance.

 

A few weeks ago I decided to avoid this waste of time and wrote a macro that would do the pasting and the formatting for me. It's a simple and tiny way to increase productivity, that allows me to focus on the things that really matter. If you like tidy code listings, I am sure you'll find this macro useful.

 

The first problem I had to solve is a limitation of the Clipboard.GetObjectData method. When invoked from a macro, this method always returns Nothing, thus I needed a different way to read the text in the Clipboard. I can surely do this with an API call or by calling a method in a separate DLL, but I thought that reading the Clipboard from a macro shouldn't be that difficult. My next attempt was based on the Paste method of the TextBox control:

 

' Retrieve the text in the clipboard

Dim tb As New TextBox

tb.Multiline = True

tb.WordWrap = False

tb.ScrollBars = ScrollBars.Both

tb.Paste()

Dim text As String = tb.Text

 

This approach works nearly always. Every now and then, in fact, the Paste method fails with a cryptic message: "Class already exists". I noticed that this error occurs only if the macro editor is open, therefore during the normal use it doesn't cause much trouble. However, once you get this error, the only way to get rid of it is by restarting Visual Studio. When I posted this first solution on my Italian blog, reader Andrea Ferendeles suggested a different approach, based on the Paste method of the TextSelection object:

 

' Read the text in the clipbard, through the Selection.Paste method. 
Dim sel As TextSelection = DirectCast(DTE.ActiveDocument.Selection, TextSelection)
Dim sp As EditPoint = sel.ActivePoint.CreateEditPoint()
sel.Paste()
' Select and read the text just pasted, then delete it

sel.MoveToPoint(sp, True)
Dim text As String = sel.Text
sel.Delete()

 

Once I solved this problem, writing the macro was relatively simple:

 

Imports EnvDTE
Imports System.Text.RegularExpressions

 

Imports CodeArchitectsMacros

 

   Public Sub PasteAsComment()
    
PasteAsComment("80")
   End Sub

   Public Sub PasteAsComment(ByVal lineLength As String)
     
Dim maxLength As Integer = CInt(lineLength)

      ' Read the text in the clipbard, through the Selection.Paste method. 
      Dim sel As TextSelection = DirectCast(DTE.ActiveDocument.Selection, TextSelection)
      Dim sp As EditPoint = sel.ActivePoint.CreateEditPoint()
      sel.Paste()
      ' Select and read the text just pasted, then delete it

      sel.MoveToPoint(sp, True)
      Dim text As String = sel.Text
      sel.Delete()

      ' Split in lines not longer than MaxLength
     
Dim result As String = ""
     
Dim currLineLength As Integer = 0
     
For Each m As Match In Regex.Matches(text, "\S+\s*")
        
If currLineLength + m.Length > maxLength Then
           
result &= ControlChars.CrLf
           
currLineLength = 0
        
End If
        
result &= m.Value
        
currLineLength += m.Length
        
If m.Value.IndexOf(ControlChars.CrLf) > 0 Then
           
currLineLength = 0
        
End If
      Next
    
  result &= ControlChars.CrLf

      ' Paste the text in the code editor
     
sp = sel.ActivePoint.CreateEditPoint()
     
sel.Insert(result)
     
sel.MoveToPoint(sp,
True)

      ' Comment and reformat it
      DTE.ExecuteCommand("Edit.CommentSelection")
     
sel.SmartFormat()
   End Sub

End Module

As you see, there are actually two macros. The version with zero arguments creates comments lines that are 80 characters or shorter; this is likely to be the version that you'll use more often and you may want to associate it with a keyboard shortcut. The version with one argument allows you to specify the line length and can be used only from the Command window. For example, the following command pastes the current Clipboard content as comments not longer than 60 characters:

 

Macros.MyMacros.UsefulMacros.PasteAsComment 60

 

You don't really have to type all these characters each time, because you can associate the command to an alias. using this command:

 

alias PasteCom Macros.MyMacros.UsefulMacros.PasteAsComment

 

Once you've created the alias, you can recall the macro as follows:

 

PasteCom 60

 


NOTE: In case you never wrote a macro in your programming life, this is how to proceed:

 

1) run the Tools-Macros-Macros IDE command (or just press Alt+F11) to bring up the Macro IDE

2) in the Macro IDE, select the MyMacros project, then issue the Projects-Add Module to create a new module that stores all your custom macros then paste the macro code inside this module. (As in previous code, most of my macros are gathered in the CodeArchitectsMacros module.)

3) Go back to Visual Studio and display the Macro Explorer window, by means of the Tools-Macros.Macro Explorer menu command (or just press Alt+F8); in the Macro Explorer window, expand the MyMacros node and then expand the CodeArchitectsMacros module

4) optionally, go to the Tools-Options dialog box to assign a keyboard shortcut to the PasteComment macro

 

You're now ready to test the macro. Switch to Notepad or Word or wherever the text is, copy it into the clipboard, switch back to Visual Studio, place the caret where you want to insert the comment, and run the macro. You can run the macro by double-clicking its node in the Macro Explorer window, by typing the keyboard shortcut (if you assigned one), or by typing the macro's name inside the Commands window (with or without its alias, see above).

 

10/30/2005 12:47:30 PM (GMT Standard Time, UTC+00:00)  #    Disclaimer  |  Comments [6]  | 
 
Get RSS/Atom Feed
RSS 2.0 | Atom 1.0
Search in the blog
Archive
<February 2006>
SunMonTueWedThuFriSat
2930311234
567891011
12131415161718
19202122232425
2627281234
567891011
Categories

Powered by: newtelligence dasBlog 1.8.5223.1