String manipulation is a very different beast in VB.NET than in VB6. In fact, I’d wager that it’s the one area that trips up programmers new to VB.NET more than any other aspect of the language.
I’ve talked about some annoying aspects of System.String before, but this go round, I’m going to concentrate on shared methods.
Shared Methods are methods that are invoked via the class type itself, and not via an instance of the class.
For instance, with strings, you have a nicely named IsNullOrEmpty function that you’d expect would return a true/false indicating whether the string in question is null or empty. Unfortunately, you’d be only half right.
Bring up the Object browser and find the String class, then highlight the IsNullOrEmpty method and you’ll see this:
Notice the Shared keyword. That indicates that this method IS NOT an instance method, thus you can’t invoke it via a specific instance of a string; rather, you must invoke it via the String class directly.
So, you can’t do this:
Dim s as string
If s.IsNullOrEmpty Then
But you can do this:
Dim s as string
If String.IsNullOrEmpty(s) Then
Now, it makes perfect sense, from a purely technical perspective, that invoking an instance method on an uninitialized object wouldn’t work anyway, so a method like IsNullOrEmpty wouldn’t make sense to be an instance method, since attempting to invoke it via a variable that hadn’t actually already been initialized would result in an “Object not Set” error.
However, this is VB, not some ivory tower exercise in theoretical language design. If I’m going to invoke a method like IsNullOrEmpty, I’m expect to be able to do so against an instance. Having to invoke it via the String class is just so utterly unintuitive, it defies all reason.
Oddly, the very argument that I note above in favor of using a shared method for IsNullOrEmpty is violated by another string property, Length. Here’s a property that is definitely an instance property, but causes VB code to fail (with the Object Not Set error) when invoked on a variable that hasn’t actually been set to value yet.
Is this just an arbitrary oversight, a design flaw, or an intentional “feature” of the language? I can’t answer that.
But, realistically speaking, I can say that it’s utterly frustrating to have elements of a language, such as these, behave so drastically different from one version to another. It doesn’t matter that the syntax is different (x.Length vs Len(x), for instance), there is an expectation there that simply is no longer met and does nothing but confuse.
Fortunately, with VB 2008, there is a relatively trivial way to correct these problems, and likely a host of other similar issues.
It’s called “extension methods”.
To create an IsNullOrEmpty that works like any reasonable person would expect it too, just put this in a Utility module somewhere in your project:
Imports System.Runtime.CompilerServices
Module StringExtensions
''' <summary>
''' Returns True if the current string instance is nothing or a null string
''' </summary>
''' <param name="aString"></param>
''' <returns></returns>
<extension ()> _
Public Function IsNullOrEmpty(ByVal aString As String) As Boolean
Return String.IsNullOrEmpty(aString)
End Function
End Module
The Imports System.Runtime.CompilerServices is only used during compilation. You can actually continue to target the .NET runtime v2.0, even if you use this code (however, you still have to compile the code from VS2008, it won’t work in VS2005).
You tag the new version of IsNullOrEmpty with the <extension()> attribute to mark it as an extension method.
The first parameter of an extension method is required and is an argument of the type of class that you’re extending, in this case the String class.
You can have additional arguments if necessary, but you don’t need any for this method.
This trick takes advantage of the fact that even though the String class already has a method named IsNullOrEmpty, the function signature is not the same as this one (since ours has the implicit first argument). This is effectively an overload and it allows VB to know to call the new method if invoked against an instance, and the old one if invoked against the String class directly (which is exactly what’s being done within the method itself!).
There are several other “shared” methods on the string class that can similarly be extended to more intuitive instance methods in this way, for instance:
- Compare
- Concat
- Format
- Join
Length could also be added to this list but you can’t quite treat it the same, since it’s a property, and the Extension attribute can’t be applied to properties.
Finally, extension methods can be unbelievably useful for associating functionality with specific classes that you can’t extend in any other way, but, as always, you need to be careful with how much you extend a class.
For example, it might be tempting to extend the String class with all sorts of path/file/folder manipulation and parsing logic, so that you could do something like this:
dim s as string = "c:\myFolder"
debug.print s.DriveLetter
but doing so could quickly clutter the String object and Intellisense.
As usual with these sorts of techniques, use judiciously.