 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.)
 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)
 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.
 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
 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!
 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."
 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.
 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.
 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.
 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()
|