|
Francesco's blog
 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 (58 pages): there is a lot to say about reflection; among the many examples I wrote an app that generates code on the fly, a scheduler for undoable actions, and a universal comparer class. 19. Custom Attributes (46 pages): this chapter includes a few complete and nontrivial examples of how a custom attribute can make your coding simpler, for example by means of Windows Forms plugins and a framework for n-tier apps. 20. Threads (54 pages): the Thread object, asynchronous delegates, thread pool, the SyncLock statement, all the synchronization types, including the new Semaphore. Plus a section on threading in Windows Forms aoos. 21. Object Serialization (32 pages): binary and SOAP serialization, version-tolerant serialization in .NET 2.0, the new attributes for serializatoin, custom serialization, serialization surrogates, the IObjectReference interface, and more. 22 PInvoke and COM Interop (40 pages): How to interact with unmanaged code: calling "classic" DLL and Windows API methods; using COM components (including the new registration-free components); writing .NET components that can be used from COM apps.
As I already explained, this book isn't a mere VB 2005 reference. Better, in addition to being a complete reference book, it is a digest of the many programming techniques that you can implement by leveraging the features of the language and the .NET Framework 2.0, including generics, threads, reflection, custom attributes, serialization, delegate, regular expressions, and more. All descriptions aim to writing faster and more robust code. I looked hard for a similar book on the market before writing this one. I believe I finally wrote a book that does VB justice.
It has been a real tour de force, which kept me busy from May, summer included. Today I have completed the very last edit to PDFs, then everything goes to the printer!
 Thursday, December 15, 2005
 |
Many readers are sending emails asking whether I was writing the 2005 edition of Programming Microsoft Visual Basic .NET. The answer is "yes and no".
Yes, I am writing the new edition of this book, updated to Visual Basic 2005.
No, I am not really writing a new edition of that book. The book I am finalizing this week is actually a brand new book, titled Programming Microsoft Visual Basic 2005: The Language. I decided to keep the title similar enough to the original one, to emphasize that the author is the same and that the approach is similar; however, I decided to added "The Language" postfix, to ring a bell in the mind of potential buyers who might otherwise believe that this is "just" an update to VB2005 and .NET 2.0.
The new book focuses only on the VB language and the .NET Base Class Library (BCL). It covers both old and new keywords as well as topics such as .NET data types, arrays and collections, streams, reflection, serialization, threading, PInvoke and COM interop. It does not cover high-level stuff such as Windows Forms, ADO.NET, and ASP.NET, though. |
I thought hard about the Table of Contents of this new book, literally for months. In the previous edition I managed to squeeze in "only" 1400 pages virtually anything you need to work with .NET Framework 1.1, including advanced topics such as serviced components and remoting, Windows Forms and Web Forms custom control creation, security, and so forth. However, .NET 2.0 is much more complex that 1.1, and I estimated that I would have either needed to split the book in two volumes or be less specific on most topics. Both choices were unsatisfactory to me.
Also, I couldn't help noticing that there are too many great books around about Windows Forms or ASP.NET programming, and a single core-reference book is bound to be less complete than those books with a narrower focus. Granted, a book from a single author and that covers all these topics can offer a unified view of what is important in .NET programming, but I am sure that developers who are deeply interested in a specific area will buy a book that specializes in that area.
While I was taking note of what else could be found on bookstore shelves, I found out that all these high-level books often overlook the basics, for example out to get the best out of the language or basic data types such as DateTime, arrays, and collections. Most .NET developers know how to write great Windows Forms or ASP.NET applications, yet they don't know how to optimize string-intensive programs effectively. And I am not talking about the usual String vs. StringBuilder example, I am thinking of techniques such as this, this, or this. Another example is memory optimization: you can speed up your code by an order of magnitude using caching techniques based on the WeakReference type, or by means of a correct Dispose-Finalize pattern. Not to mention advanced techniques based on delegates and reflection, such as this, this,or this.
In the end, I realized that I could write a very good book on just the Visual Basic language and the most important facets of the BCL. Rather than (or in addition to) being a plain reference for VB keywords and .NET types, this book is more similar to a complete compendium of programming techniques that you can implement with these features. For example, there is one entire chapter devoted to custom attributes, with a few advanced examples of how they can help you in the design of your n-tier apps. In other words, instead of just listing what are your tools this book will explain how to leverage them using intermediate-to-advanced techniques, including nonobvious techniques based on generics, on-the-fly compilations, advantage use of delegates and custom events, and so forth.
Another important topic that Programming Visual Basic 2005: The Language book covers is Visual Studio and how to take advantage of its many editing and testing features. The book includes two long chapters (112 pages in total) which covers basic and advanced topics, including macro creation, unit testing (with Team System), debugging techniques, and more. I have never found a language book that focuses on productivity inside the IDE and I hope this new book fills this gap.
The book consists of 22 chapters, for more than 1000 pages. I'll publish its Table of Contents in another post very soon, hopefully tomorrow. |
 Wednesday, December 14, 2005
In a previous post I introduced a tiny utility to clean up Visual Studio projects. I have introduced minor fixes to it since then, so this is the most recent version, which also deletes *.suo and *.user files and produces a report of all the files and folders that couldn't be deleted for any reason:
Imports System.IO
Module Module1 Sub Main(ByVal args() As String) Console.WriteLine("VSProjCleaner tool (C) 2005 Francesco Balena, Code Archirects") If args.Length = 0 Then Console.WriteLine("Removes BIN and OBJ folders, .suo and .user files from Visual Studio projects") Console.WriteLine() Console.WriteLine(" SYNTAX: VSProjCleaner dirname") Console.WriteLine() Return End If Console.WriteLine()
' Use current directory if no argument has been specified Dim rootDir As String = Directory.GetCurrentDirectory() If args.Length > 0 Then rootDir = args(0) ' Read all the folder names in the specified directory tree Dim dirNames() As String = Directory.GetDirectories(rootDir, "*.*", SearchOption.AllDirectories) Dim errorsList As New List(Of String)
' delete any .suo and vbproj.user file For Each dir As String In dirNames Dim files As New List(Of String) files.AddRange(Directory.GetFiles(dir, "*.suo")) files.AddRange(Directory.GetFiles(dir, "*.user")) For Each fileName As String In files Try Console.Write("Deleting {0} ...", fileName) File.Delete(fileName) Console.WriteLine("DONE") Catch ex As Exception Console.WriteLine() Console.WriteLine(" ERROR: {0}", ex.Message) errorsList.Add(fileName & ": " & ex.Message) End Try Next Next
' Delete all the BIN and OBJ subdirectories For Each dir As String In dirNames Dim dirName As String = Path.GetFileName(dir).ToLower() If dirName = "bin" OrElse dirName = "obj" Then Try Console.Write("Deleting {0} ...", dir) Directory.Delete(dir, True) Console.WriteLine("DONE") Catch ex As Exception Console.WriteLine() Console.WriteLine(" ERROR: {0}", ex.Message) errorsList.Add(dir & ": " & ex.Message) End Try End If Next Console.WriteLine() Console.WriteLine(New String("-"c, 60)) If errorsList.Count = 0 Then Console.WriteLine("All directories and files were removed successfully") Else Console.WriteLine("{0} directories or directories couldn't be removed", errorsList.Count) Console.WriteLine(New String("-"c, 60)) For Each msg As String In errorsList Console.WriteLine(msg) Next End If End Sub End Module
You can download the binary version here: VSProjCleaner.exe (24 KB) or the complete solution here: VSProjCleaner_Source.zip (15.08 KB)
Using this tool couldn't be simpler: just run it from the command window and pass a folder name as an argument, enclosing it in double quotes if it contains spaces: VSPROJCLEARNER "c:\my projects\testproj" The tool recursively visits all the folders and deletes all BIN and OBJ directories, and deletes *.suo and *.user files. At the end of the process it lists all the folders and files that couldn't be removed, if any.
DISCLAIMER: I have tested this code against my own projects and it always worked correctly, but I don't should be held responsible for any loss of important files (for example, a .user file that has nothing to do with a Visual Studio project.). For this reason, I urge you to apply this tool only to a COPY of your projects
 Thursday, December 08, 2005
 |
Last spring I co-authored this book, Practical Guidelines and Best Practices for Microsoft Visual Basic .NET and Visual C# Developers, arguably the longest title in Microsoft Press's history. The book is a reasoned list of guidelines that all .NET developers should follow, actually is by far the largest collection of its kind you can find anywhere. It covers language syntax, memory usage, Windows Forms and ASP.NET applications, security, and more.
Unlike most other similar collections, though, we clearly divide the "rules" in guidelines (naming guidelines, comment usage, etc.) and best practices. The difference is subtle but important: most guidelines are primarily a style matter, whereas best practices impact the scalability, the speed, or the robustness of your application. This means that our guidelines are arbitrary and in fact we often offer alternate rules and clearly explain the pros and cons of each style.
You can learn more about the principles we used in the book's Introduction and in John Robbins's Foreword. (Unlike most foreword writers, John actually read each and every page in the manuscript and gave us some great advice about improving it.) Or click the figure to jump to the book's home page, where you can read three sample chapters and download the book's source code.
Today I have uploaded a 30-page Word document that contains a summary of all the rules covered in the book, orderly grouped by topic and with a reference where in the book each rule is explained. You can edit this document as you see fit, delete or edit the guidelines you aren't interested in, and so forth. We routinely use this document in internal code reviews or when we consult at customers' places, so we hope it will be useful to you as well.
P.S. You must register to access this material. We swear we'll never send you anything that vaguely resemble spamming, just 100% technical contents!
|
 Saturday, December 03, 2005
Consider the following code, that converts all the elements of an Int32 array into the corresponding hex value:
' VB Dim intArray() As Integer = {4, 6, 9, 10, 99, 233, 34, 88, 189} Dim hexArray(intArray.Length - 1) As String For i As Integer = 0 To intArray.Length - 1 hexArray(i) = intArray(i).ToString("X") Next
// C# int[] intArray = {4, 6, 9, 10, 99, 233, 34, 88, 189}; string[] hexArray = new string[intArray.Length]; for ( int i = 0; i < intArray.Length; i++) { hexArray[i] = intArray[i].ToString("X"); }
The question is: how can you make this code more concise in .NET 2.0? The first answer that might come up is to use the Array.ConvertAll method together with a C#'s anonymous method:
string[] hexArray = Array.ConvertAll<int,string>(intArray, new Converter<int,string>( delegate(int n) { return n.ToString("X");}));
Actually, you can write even more concise code if you remember than the Microsoft.VisualBasic library already contains the Hex method, which matches the signature of the Converter<int,string> delegate. Using this method and delegate inference, you can shrink the code to:
' VB Dim hexArray() As String = Array.ConvertAll(Of Integer, String)(intArray, AddressOf Hex) // C# string[] hexArray = Array.ConvertAll<int,string>(intArray, Microsoft.VisualBasic.Conversion.Hex );
I am certain that few C# developers will use this trick, but I thought it was worth mentioning. (Of course, you must add a reference to the Microsoft.VisualBasic.dll assembly if you work with C#.) The key idea, however, is that in some cases you don't need to write an anonymous method to accomplish a given task, because often you can find what you're looking for in the .NET Framework. For example, you can display all the elements of an array in the Console window with just one statement:
' VB Array.ForEach(hexArray, AddressOf Console.WriteLine) // C# Array.ForEach(hexArray, Console.WriteLine);
There are many other methods in the VB library that you can use to convert all the elements of an array or a generic List, including UCase, LCase, LTrim, RTrim, and Trim.
 Thursday, December 01, 2005
One of the .NET Framework features that fascinate me most is regular expressions, which I often use to simplify and speed up my applications. Well, at least this is what I believed until some time ago, when I was busy writing the forthcoming Programming Microsoft Visual Basic 2005: The Language (due in mid-January). This book is a core reference on the VB language and includes a section on the LIKE operator, which in recent years a overlooked in favor of regexes. I (mistakenly) assumed that the Like operator internally used the Regex classes, therefore surely it would have been slower. After all these years, I should have learned that I should never jump to conclusions without testing and benchmarking my code accurately. .
Let's say that you must check that a string has 9 characters, the first of which must be an uppercase "A" and the last four chars must be digits. This is how you'd perform this test with a regex:
Dim re As New Regex("^A....\d\d\d\d$")
and here's the version that uses the Like operator:
If teststring Like "A????####" Then Match = True
Surprise! Putting this code in a loop (but leaving the creation of the regex out of the loop) and using a string that makes the test succeed (e.g. "ABCDE1234"), the Like operator is about 4 times faster than the regular expression. Not bad, uh? But the biggest surprise came when I benchmarked the same test based on methods of the System.Char class exclusively:
If teststring.Length = 9 AndAlso teststring.Char(0) = "A"c AndAlso Char.IsDigit(teststring.Char(5)) Then AndAlso Char.IsDigit(teststring.Char(6)) AndAlso Char.IsDigit(teststring.Char(7)) AndAlso Char.IsDigit(teststring.Char(8)) Then match = True
Despite of its length, this last test is about five times faster than the Lik operator, and therefore about 20 times faster | | |