Quick intro to extension methods:
We could say extension methods are a special kind of methods, with capability of extending existing types, they apply to. What does that mean? It means that you can extend functionality of every existing .NET built-in type, your own custom types, or any third party type, even if you don't have access to the source code. Pretty powerful, ain't it?
Let's take a look at really simple example of extension method. We will extend .NET built-in Double type in a way, so we won't need to transform angular degrees to radians, each time we want to calculate sine of given angle, when our value is in degrees (because Math.Sin function takes argument of Double, that should represent value in radians).
Converting degrees to radians is done by multiplying degree value with Math.PI/180.
Let's have a look, how we would write our degrees to radians function, in "ordinary" way first:
Function DegreesToRadians(ByVal angle As Double) As Double Return angle * Math.PI / 180 End Function
So now, if we want to get sine of the angle, when we have our angle value in degrees, we'd need to send this value as angle parameter to DegreesToRadians function, and assign return value to some variable, that would hold transformed value from degrees to radians. Something like this:
Const valueInDegrees As Double = 5.234 Dim valueInRadians As Double = DegreesToRadians(valueInDegrees) Dim sinFromDegrees As Double = Math.Sin(valueInRadians)
We can validate our conversion and sine calculation with Windows OS built-in calculator:
With extension methods, we can do something even better than what we already did. Let's have our own SinD extension function, where we will be able to calculate sine of our value, we have in degrees.
We will add another function, name SinD to the module, and make both SinD and DegreesToRadians as extensions. Note from MSDN:
The first parameter in an extension method definition specifies which data type the method extends. When the method is run, the first parameter is bound to the instance of the data type that invokes the method.
Ordinary method becomes extension method, if we decorate them with: Runtime.CompilerServices.Extension(). We can extend only Sub and Function methods (so you can't extend Get and Set methods of Property for example):
<Runtime.CompilerServices.Extension()> Function SinD(ByVal angle As Double) As Double Return Math.Sin(angle.DegreesToRadians) End Function <Runtime.CompilerServices.Extension()> Function DegreesToRadians(ByVal angle As Double) As Double Return angle * Math.PI / 180 End Function
Now we can consume our SinD extension method with something like:
Dim sinFromDegrees As Double = (5.234).SinD
And, as we can verify, the result is correct, as it should be:
Final note on extensions: VB.NET allows extensions to be defined in modules only, because modules are considered to be equal to static classes in C#, and this is important because all their members could be accessed without instantiating such classes (you don't need to use New keyword to use its members). This means extension methods are considered as shared methods by compiler.
More about extensions:
Extension Methods on MSDN
Some samples of extension methods in VB.NET
VB.NET code snippets on D.I.C. site (this site that is). I'd like to say something like: "Use site's search function on the top right", but perhaps I'm blind, or there really is no snippet search functionality... I don't know.
Anyway, let's dig a bit deeper now!
Why would you want to have the same functionality as extension function, available to non-integral numeric data types only, and how you can do it?
Did you ever try to create unit converter? Meters to inches, or less known units like furlongs... Hectares to square yards... Wouldn't it be nice, if .NET would have something like myNumber.ConvertDistance(LenghtUnits.kilometers, LenghtUnits.miles)?
You can do that quickly with extension function, like:
<System.Runtime.CompilerServices.Extension()> Public Function ConvertDistance(ByVal number As Decimal, ByVal convertFrom As LenghtUnits, ByVal convertTo As LenghtUnits) As Decimal Return 'do the conversion End Function 'for the sake of simplicity, we are going to assume miles are UK/US non nautical miles Public Enum LenghtUnits kilometers miles End Enum
Now ConvertDistance becomes available through IntelliSense to all declared members of Decimal type. Nice.
Extending method's availability:
Let's add some real functionality to our distance converting extension, and make it useful.
As you can see in spoiler above, methods can grow quickly. We now have fully functional distance unit converting extension function, but it is available to Decimal members only. If you try for example:
Dim myNumber As Integer = 5 myNumber.ConvertDistance(LenghtUnits.yards, LenghtUnits.kilometers_km)
Error 'ConvertDistance' is not a member of 'Integer'
Now what? Shall we copy/paste ConvertDistance and ToMeters function, and just replace Decimal to Short, than to Integer, Long, Single, Double, UShort, UInteger, and ULong (and potentially any other numeric type, I may have missed here)?
There is no INumeric interface, or IFloat and similar, that would allow you to write extensions to such numeric types only.
What we can try, is change our ConvertDistance extension, so it will accept generic types:
<System.Runtime.CompilerServices.Extension()> Public Function ConvertDistance(Of T)(ByVal number As T, ByVal [from]As LenghtUnits, ByVal [to] As LenghtUnits) Dim _number As Decimal If Not Decimal.TryParse(number.ToString, _number) Then Throw New Exception("Parameter ""number"" should be numerical type.") End If Select Case [from] Case LenghtUnits.Ångströms_Å Return _number.ToMeters([to]) * 0.0000000001 Case LenghtUnits.AstronomicalUnits_AU Return _number.ToMeters([to]) * 149598550000 '... etc
This will work for numeric values, passed as number into ConvertDistance, but there are several issues with such solution:
- it becomes available to any member type, even to our custom types (like MyStrangeCustomClass,...), and throws exception on runtime only;
- it is not strongly typed (saying the same as above, with other words), and may cause disapproval of your fellow programmers, who will use your pack of unit converting extensions as .dll;
- it just isn't "nice", and doesn't "feel right"
Restricting method's availability:
What we can do now is no magic, and is essentially just method overloading, where we get several overloaded extension functions for each desired value type, that simply "redirect" all other types to what we already have:
'short <System.Runtime.CompilerServices.Extension()> Public Function ConvertDistance(ByVal number As Short, ByVal [from] As LenghtUnits, ByVal [to] As LenghtUnits) As Short Return CDec(number).ConvertDistance([from], [to]) End Function 'integer <System.Runtime.CompilerServices.Extension()> Public Function ConvertDistance(ByVal number As Integer, ByVal [from] As LenghtUnits, ByVal [to] As LenghtUnits) As Integer Return CDec(number).ConvertDistance([from], [to]) End Function 'and of course our seed method, available to Decimal data types stays the same as before: <System.Runtime.CompilerServices.Extension()> Public Function ConvertDistance(ByVal number As Decimal, ByVal [from]As LenghtUnits, ByVal [to] As LenghtUnits) As Decimal Select Case [from] Case LenghtUnits.Ångströms_Å Return number.ToMeters([to]) * 0.0000000001 Case LenghtUnits.AstronomicalUnits_AU Return number.ToMeters([to]) * 149598550000 'etc
Now we can use ConvertDistance extension with Short and Integer member types, too. In the same manner, we create all other extensions for Long, Double... types. It's nothing new, no rocket science, but nice design work-around, that might help you some day.
Here's the whole code in one place: