Francesco's blog

 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 [1]  | 
 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 [0]  | 
 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 [5]  | 
 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 [3]  | 
 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