 Monday, May 01, 2006
Many, if not most, Windows Forms samples you can find on the 'net include one or more calls to unmanaged code in Windows DLLs, often in the form of calls to the SendMessage API methods to fix some of the (very few) missing features of .NET controls. The problem is, such a call to unmanaged code creates a problem when the program runs as a ClickOnce application, because it requires higher CAS privileges.
Even though this problem doesn't have a generic solution, when you just need to send a message to the control you are inheriting from, you can avoid an explicit call to SendMessage by invoking the protected DefWndProc method instead. For example, let's say that you are writing an enhanced ComboBox that exposes the TopIndex property, which can set or return the index of the first visible item in the list area. These two operations can be implemented by sending the control the CB_SETTOPINDEX or CB_GETTOPINDEX message, respectively. Here's how you can use the DefWndProc method instead of SendMessage:
Public Class ComboBoxEx Inherits System.Windows.Forms.ComboBox
Public Property TopIndex() As Integer Get Const CB_GETTOPINDEX As Int32 = &H15B Dim m As New Message() m.HWnd = Me.Handle m.Msg = CB_GETTOPINDEX Me.DefWndProc(m) Return m.Result.ToInt32() End Get Set(ByVal value As Integer) Const CB_SETTOPINDEX As Int32 = &H15C Dim m As New Message() m.HWnd = Me.Handle m.Msg = CB_SETTOPINDEX m.WParam = New IntPtr(value) Me.DefWndProc(m) End Set End Property
End Class
By the way, such an enhanced ComboBox can be useful when migrating a VB6 app to VB.NET. In fact, the VB6 ComboBox and ListBox controls expopse the TopIndex property, whereas under .NET only the ListBox control exposes this property. If you have any VB6 code that takes advantage of the ComboBox's TopIndex, the simplest approach is replacing the standard ComboBox with a ComboBoxEx instance.
 Saturday, April 29, 2006
MsgHookX is an ActiveX DLL that allows VB6 developers to perform safe and efficient intra-applicaton subclassing. (In this context, intra-application sublclassing means that you can intercept any message that Windows sends to a window or a control in the current application, as opposed to a window created by another application, a much more difficult task.) I wrote this DLL many years ago, when VB5 was released, and mentioned it in several books and magazine articles. I made the DLL available on the vb2themax site, but I omitted to upload the DLL in this new, .NET-only site.
Well, given the many mails I continue to receive from readers, it seems that VB6 is still alive and in good shape, and many VB6 developers continune to happily subclass their controls. For this reason I decided to make the DLL available again on this site. Here's a summary of what it does:
- safe subclassing: can be used within the IDE and in break mode without any risk of system crashes.
- it provides the BeforeMessage and AfterMessage events for easy event-driven programming model
- it additionally can notify incoming messages through the IMsgHookEvents secondary interface, for better performance and easier debugging (in some cases, events are inhibited in the IDE)
- highest flexibility: you can decide to call the original window procedure yourself from within the BeforeMessage event/method and/or cancel the default processing for the message. You can also browse and modify the value that will be returned to the operating system.
- the DLL's type library includes the definition of over 300 symbolic constants that define the most common Windows messages, so you don't have to use the API Viewer to include them in your applications.
Have fun with it, but - please, please! - turn to VB.NET as soon as you can, if you haven't yet.
I am working at the migration of a large VB6 project and at one point I needed to drop a few methods in the VB6 app, in such a way that the methods would be "invisible" to VB.NET. This action was necessary because these methods performed similarly to a .NET native method. Obviously, I had the option to manually delete the methods after the migration, but if their number is high and if you need to run the wizard more than once on the same VB6 app (as it happens frequently, in the process of preparing the app for the migration), then deleing these portions of code each time becomes a nuisance.
Apparently, you can't instruct the migration wizard to ignore one or more pieces of code. Nevertheless, the solution is quite simple: you just need to bracket these pieces of code - entire methods or just individual statements - in a #If Win32 ...#End If block. The Win32 compilation constant - that is probably ignored by most VB developers today - was introduced when Visual Basic 4 was released, and was recognized also by VB5 and VB6 (but not by any version of VB.NET). Visual Basic 4 is the only version of this language that is available in 16-bit and 32-bit edition, and this compilation constant (together with Win16) allowed to define blocks of code that were compiled under only one of those versions, while allowing to mantain a single source code for both. Therefore, the following VB6 code:
#If WIN32 Then Private Sub Do Something(ByVal n As Integer) ' ... End Sub #End If
is correctly migrated to VB.NET by the migration wizard as follows:
#If WIN32 Then Private Sub DoSomething(ByVal n As Short) ' ... End Sub #End If
but this piece of code will be skipped over by the VB.NET compiler because the Win32 constant isn't defined under VB.NET.
This "feature" can actually create a problem if you are migrating a VB6 app that has been evolved from an older application that was originally written in VB4. In this case, in fact, it is possible that its source code contains one or more #If Win32 blocks. In most cases you *want* to migrate this code, but these portions will be ignored after the migration to VB.NET. If this is the case, you should then locate all the the occurrences of #If Win32 statements in the code and delete them before the migration.
 Monday, March 06, 2006
I am reorganizing my MP3 collection and found that I needed to rename a large quantity of files. Of course, there are many free utilities that allow this operation - and that can use MP3 tags in the process - but I thought that I might write one myself. Thanks to regular expressions, the task shouldn't be that hard. In fact, in a few minutes I came up with the following console application. As you see, most of the code is used to extract and validate arguments on the command line:
Imports System.Text.RegularExpressions Imports System.IO
Module Renx
Function Main(ByVal args() As String) As Integer Console.WriteLine("RENX (C) Francesco Balena / Code Architects Srl")
Dim recurse As Boolean = False Dim renameMode As Boolean = False Dim oldNamePattern As String = Nothing Dim newNamePattern As String = Nothing
' analyze each argument For Each arg As String In args Select Case arg.ToLower() Case "/s", "-s" recurse = True Case "/r", "-r" renameMode = True Case "/h", "-h" Return ShowHelp(0) Case Else If oldNamePattern Is Nothing Then oldNamePattern = "^" & arg & "$" ElseIf newNamePattern Is Nothing Then newNamePattern = arg Else Return ShowHelp(1) End If End Select Next
' check that we have both mandatory arguments If oldNamePattern Is Nothing OrElse newNamePattern Is Nothing Then Return ShowHelp(1) End If ' create the regex and check that pattern syntax is ok Dim reSearch As Regex Try reSearch = New Regex(oldNamePattern, RegexOptions.IgnoreCase) ' test the replace pattern as well Dim tmp As String = reSearch.Replace("a dummy string", newNamePattern) Catch ex As Exception Console.WriteLine("SYNTAX ERROR: {0}", ex.Message) Return 3 End Try Console.WriteLine()
' iterate over all files in current directory (and its subdirectories, if recurse mode) Dim searchOpt As SearchOption = SearchOption.TopDirectoryOnly If recurse Then searchOpt = SearchOption.AllDirectories
Dim parsedFilesCount As Integer = 0 Dim renamedFilesCount As Integer = 0 Dim errorsCount As Integer = 0 For Each oldFile As String In Directory.GetFiles(Directory.GetCurrentDirectory(), "*.*", searchOpt) parsedFilesCount += 1 ' the regex applies to name only Dim oldName As String = Path.GetFileName(oldFile) Dim ma As Match = reSearch.Match(oldName) If ma.Success Then ' this is the new name Dim newName As String = ma.Result(newNamePattern) Console.WriteLine(oldFile) Console.Write(" => {0}", newName) renamedFilesCount += 1 ' proceed with rename only if not in simulation mode If renameMode Then Try Dim dirName As String = Path.GetDirectoryName(oldFile) Dim newFile As String = Path.Combine(dirName, newName) File.Move(oldFile, newFile) Catch ex As Exception Console.Write(" -- ERROR: {0}", ex.Message) errorsCount += 1 End Try End If Console.WriteLine() End If Next
' Display a report If renameMode Then Console.WriteLine("Summary: {0} parsed files, {1} renamed files, {2} errors", parsedFilesCount, renamedFilesCount, errorsCount) Else Console.WriteLine("Summary: {0} parsed files, {1} files affected", parsedFilesCount, renamedFilesCount) Console.WriteLine() Console.WriteLine("NOTE: Running in simulation mode. Specify the /R option to actually rename files.") End If ' Return an error code If errorsCount = 0 Then Return 0 Else Return 2 End If End Function
Function ShowHelp(ByVal exitCode As Integer) As Integer Console.WriteLine() Console.WriteLine("Syntax: RENX <oldnamepattern> <newnamepattern> [/R] [/S] [/H]") Console.WriteLine(" oldnamepattern : regex that selects the files to be renamed") Console.WriteLine(" newnamepattern : regex that specifies how files must be renamed") Console.WriteLine(" /R : rename files") Console.WriteLine(" /S : iterate over subdirectories") Console.WriteLine(" /H : display this help") Console.WriteLine("NOTE: By default the program runs in simulation mode, and just displays how files would be renamed.") Console.WriteLine(" You must specify the /R option to actually rename the files.") Return exitCode End Function
End Module
At the very minimum, the RENX utility requires two arguments: a regex that specifies which files in the current directory (and its subdirectories, if you add the /S option) must be renamed, and a second regex that specifies how to rename the files that are matched by the first regex. The power of RENX is the fact that the first regex can (actually, must) specify one or more groups of characters, and these groups are then referenced in the second regex. For example, let's suppose that I have a folder with the following files:
01 Speak to Me.mp3 02 On the Run.mp3 03 Time.mp3 04 The Great Gig in the Sky.vb3 05 Money.mp3 06 Us and Them.mp3 07 Any Colour You Like.vbr 08 Brain Damage.mp3 09 Eclipse.vb3
and that I want to rename them as follows:
01 - Speak to Me - The Dark Side of the Moon.mp3 02 - On the Run - The Dark Side of the Moon.mp3 03 - Time - The Dark Side of the Moon.mp3 04 - The Great Gig in the Sky - The Dark Side of the Moon.vbr 05 - Money - The Dark Side of the Moon.mp3 06 - Us and Them - The Dark Side of the Moon.mp3 07 - Any Colour You Like - The Dark Side of the Moon.vb3 08 - Brain Damage - The Dark Side of the Moon.mp3 09 - Eclipse - The Dark Side of the Moon.vbr
Here's the RENX command that does it:
RENX "(\d\d) (.+?)(\..+)" "${1} - ${2} - The Dark Side of the Moon.${3}"
Notice that the first regex creates three groups by enclosing them in parenthesis: (\d\d) matches the song number, (.+?) matches the song title, and (\..+) matches the file extension, dot included. The second argument can then reorder these three groups, using the ${N}, where N is the position of the group as specified in the first regex. It is therefore to insert a dash after the song number, and the albumname after the song title.
Because the RENX utility is quite dangerous, by default it doe NOT rename the files, and it just lists how files would be renamed. To actually proceed with the rename operation, you must specify the /R option:
RENX "(\d\d) (.+?)(\..+)" "${1} - ${2} - The Dark Side of the Moon${3}" /R
That's all. You can play with the source code to extend the RENX utility as you prefer, and maybe turn it into a Windows Form application, or you can download the binary version from this link: Renx.zip (5.51 KB)
 Tuesday, February 28, 2006
A few minutes ago I made all the code samples for Programming Microsoft Visual Basic 2005: The Language available on the book home page. It's a 7.5M download that contains several thousand lines of carefully crafted, optimized, reusable source code, including the following:
- a VB class that works with fractions
- a base form for simplified data entry
- a class to cache text files
- several custom iterators for better For Each loops
- tons of examples with generics
- two expression evaluators, the former built on regexes, the latter implemented via on-the-fly compilation
- a utility to display project statistics (a demo for regexes)
- a RegexTester stand-alone utility, to help you build, test and compile regular expressions
- an example of custom provider for My.Settings
- an example of how you can intercept ANY event from ANY set of Windows Forms controls
- attribute-based benchmarks
- a complete infrastructure for writing Windows Forms plug-ins
- an attribute-based library to write data-centric N-tier applications
- several Visual Studio macros
- Visual Studio visualizers for files, regex, and images
- .... and a lot more
On the same page you can also find an errata document for typos and mistakes found after the book went to print.
Enjoy the code (and buy the book if you like it! )
 Thursday, February 23, 2006
The longer I work with generics, the more I like them, and I continue to discover new ways to simplify my code by using them. More specifically, I like the ability to write type-safe code that is also more concise, efficient, and (above all) readable, because I don't have to use tons of CType and DirectCast operators. Today I gathered the generic methods I use more frequently in the following module. They are tiny and simple, yet they save me a lot of time and code.
Module GenericFunctions
' Swap two variables Public Sub Swap(Of T)(ByRef var1 As T, ByRef var2 As T) Dim tmp As T = var1 var1 = var2 var2 = tmp End Sub
' Type-safe version of the IIF function ' returns valueOnTrue if expression is True, else returns valueOnFalse Public Function IIf(Of T)(ByVal expression As Boolean, ByVal valueOnTrue As T, ByVal valueOnFalse As T) As T If expression Then Return valueOnTrue Else Return valueOnFalse End If End Function
' Type-safe version of the Choose function ' returns the N-th element of a list of values, or the default value for T if index ' is less than 0 or higher than the number of values Public Function Choose(Of T)(ByVal index As Integer, ByVal values() As T) As T If index >= 0 AndAlso index < values.Length Then Return values(index) Else Return Nothing End If End Function
' Return an array of the specified type Public Function NewArray(Of T)(ByVal ParamArray values() As T) As T() Return values End Function
' Return the min value of a list Public Function Min(Of T As IComparable)(ByVal firstValue As T, ByVal ParamArray values() As T) As T Dim result As T = firstValue For Each value As T In values If result.CompareTo(value) > 0 Then result = value Next Return result End Function
' Return the max value of a list Public Function Max(Of T As IComparable)(ByVal firstValue As T, ByVal ParamArray values() As T) As T Dim result As T = firstValue For Each value As T In values If result.CompareTo(value) < 0 Then result = value Next Return result End Function
' Return True if a value is in specific range Public Function InRange(Of T As IComparable)(ByVal testValue As T, ByVal minValue As T, ByVal maxValue As T) As Boolean Return testValue.CompareTo(minValue) >= 0 AndAlso testValue.CompareTo(maxValue) <= 0 End Function
' Retrieve a dictionary element of a given type, or the provided default value if the element isn't found ' (two overloads) Public Function GetDictionaryValue(Of TKey, TValue)(ByVal dict As Hashtable, ByVal key As TKey, ByVal defaultValue As TValue) As TValue If dict.ContainsKey(key) Then Return CType(dict(key), TValue) Else Return defaultValue End If End Function
Public Function GetDictionaryValue(Of TKey, TValue)(ByVal dict As Dictionary(Of TKey, TValue), ByVal key As TKey, ByVal defaultValue As TValue) As TValue ' If the key is in the dictionary, the following statement stores the corresponding value ' in defaultValue, else it leave defaultValue unchanged dict.TryGetValue(key, defaultValue) Return defaultValue End Function
End Module
Most methods are self-explanatory. One of the most useful ones is NewArray, which lets you create an array and pass it on-the-fly to a method. Let's say the the DoSomething method takes an array of Integers. These are the options you have in VB2005:
' 1. Create the array first, than pass it Dim values() As Integer = {1, 2, 3, 4, 5} DoSomething(values)
' 2. Create the array on the fly using the nearly-undocumented syntax DoSomething(New Integer() {1, 2, 3, 4, 5})
I often use the second syntax, but I noticed that relatively few developers know it. My code is much more readable with the NewArray method
' 3. Use the NewArray generic function to create the array on the fly DoSomething(NewArray(1, 2, 3, 4, 5))
The NewArray method proves to be quite useful also to build For loops whose index can take any sequence of values:
' Test whether "number" is a prime number in the range 1-1000 For Each n As Integer In NewArray(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31) If (number Mod n) = 0 Then Console.Write("{0} is not prime", number): Exit For Next
 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.
 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.
|
 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! 
 Monday, January 16, 2006
If I could get istantaneous results for the following simple two-question survey
- Is Visual Studio the application that you use most often?
- 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.)

 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". |
 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.
 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 |