One of the challenges in introducing new language features into our code base is that they're not always well understood. As a result, some interesting discussions come up.
Today's subject is extension methods -- what are they, how to write them and how to use them.
Let's start by examining a piece of code I wrote while doing some dynamic SQL parsing. There was a lot of code to remove the trailing " AND " from my criteria strings that looked like:
s = s.Substring(0, s.Length - 5)
I got tired of having to code this, since I always have problems with off-by-one errors when parsing strings. To keep from having to debug my error-prone code repeatedly, I wrote a quick little generic function. (Only the last line of the function is important; the other two lines are defensive coding to make the function more robust.)
Public Function RemoveLastCharacters(ByVal s As String, ByVal charsToRemove As Integer) As String
charsToRemove = Math.Max(0, charsToRemove)
charsToRemove = Math.Min(s.Length, charsToRemove)
Return s.Substring(0, s.Length - charsToRemove)
End Function
Now I could do the same thing by calling this function:
s = RemoveLastCharacters(s, 5)
Once I had this function written, I saw other spots I could use this same functionality. And I was sure I wasn't the only one. I exposed the functionality to the other developers on our team using a compiler trick called an "extension method." By attributing the function, I can tell the compiler to "extend" the type of the first parameter in the method signature.
This is an extremely powerful technique that allows developers to extend types, even without having that type's source code. (Note that this is a key language feature to support LINQ). With only a minor change, all of the developers can see my function in Intellisense whenever they are using strings.
<System.Runtime.CompilerServices.Extension()> _
Public Function RemoveLastCharacters(ByVal s As String, ByVal charsToRemove As Integer) As String
charsToRemove = Math.Max(0, charsToRemove)
charsToRemove = Math.Min(s.Length, charsToRemove)
Return s.Substring(0, s.Length - charsToRemove)
End Function
Although the function itself didn't change, the extension method becomes more discoverable. The syntax for calling the extension method is straight-forward:
s = s.RemoveLastCharacters(5)
One point worth noting is that this is still a function, exposed like any other function, so the previous syntax is still valid. I tend to prefer the last syntax, however -- to me, it's simply more readable. And, with careful use of intuitive naming conventions and return types, you can chain extension methods together to create a Fluent Interface. (This is a particularly nice technique when using third-party control libraries with challenging syntax. Chad Myers shows an example for Infragistics controls here.)
Another example of the use of extension methods is this function, which allows division of one decimal by another without worrying about divide-by-zero exceptions:
<System.Runtime.CompilerServices.Extension()> _
Public Function SafeDivideBy(ByVal numerator As Decimal, ByVal denominator As Decimal) As Decimal
If denominator = 0 Then
Return 0
Else
Return numerator / denominator
End If
End Function
Note that this function returns zero if the denominator is 0. The syntax to use this extension method is:
Dim x As Decimal = 8D
Dim y As Decimal = x.SafeDivideBy(2) ' Result 4
Dim z As Decimal = x.SafeDivideBy(0) ' Result 0