Page 1 of 1

Restricting Extension Function To Selected Value Types Rate Topic: -----

#1 lucky3  Icon User is offline

  • Friend lucky3 As IHelpable
  • member icon

Reputation: 231
  • View blog
  • Posts: 769
  • Joined: 19-October 11

Posted 18 November 2012 - 11:11 AM

*
POPULAR

Restricting Extension Function To Selected Value Types


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:

Attached Image

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:

Quote

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:

Attached Image

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!




The Problem:

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.

Spoiler


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)


you'll get:

Quote

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:
Spoiler


Is This A Good Question/Topic? 6
  • +

Replies To: Restricting Extension Function To Selected Value Types

#2 TechKid  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 3
  • View blog
  • Posts: 82
  • Joined: 04-September 10

Posted 25 November 2012 - 09:43 AM

Nice tutorial, it helps a lot! :)
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1