2

In VB.NET if I want to have an extension method for numerical variables of different types (Integer, Long, Decimal, Double) I always have to define multiple methods for these:

<Extension()> Public Function Add(a As Integer, b As Integer) As Integer
    Return a + b
End Function

<Extension()> Public Function Add(a As Long, b As Long) As Long
    Return a + b
End Function

<Extension()> Public Function Add(a As Double, b As Double) As Double
    Return a + b
End Function

<Extension()> Public Function Add(a As Decimal, b As Decimal) As Decimal
    Return a + b
End Function

Now for one single operation this is alright, but the more methods I want to create the more duplicates I have do to, too.

Is there a generic way to do so? I would love to see something like this (pseudo-code):

<Extension()> _
Public Function Add(Of T As Numeric)(a As T, b As T) As T
    Return a + b
End Function

Or is there any other concept for doing so?

jor
  • 2,058
  • 2
  • 26
  • 46
  • StevenDoggart was right and my answer wasn't applicable here, because of this: http://stackoverflow.com/questions/2402660/why-does-this-extension-method-throw-a-nullreferenceexception-in-vb-net As said via comment, under your specific conditions, I would rely on double (+ eventual casting to Integer/Decimal) to make things simpler. – varocarbas Aug 07 '13 at 14:17

4 Answers4

2

This cannot be done, because you cannot constrain a generic type to a group of numeric types (Integer, Long, Decimal, Double). The problem is that there is no IArithmetic interface that you could you use to constrain T to, therefore you cannot write this:

' This does not work because IArithmetic does not exist
<Extension()> _
Public Function Add(Of T As IArithmetic)(a As T, b As T) As T
    Return a + b
End Function

However, you can join the cause to convince Microsoft to implement this by the Microsoft Feedback Center and proposing and/or commenting on similar requests.

Karl Anderson
  • 34,606
  • 12
  • 65
  • 80
1

Jon Skeet solved this with Operator<T> in Generic Operators

public static class Operator
{
   public static T And<T>(T value1, T value2)
   {
      return Operator<T>.And(value1, value2);
   }
}

public static class Operator<T>
{
   public static Func<T, T, T> And { get { return and; } }
   static Operator()
   {
      add = ExpressionUtil.CreateExpression<T, T, T>(Expression.Add);
   }
}

@Jeroen Vannevel has a great answer on how to solve this using a T4 template to generator methods. Is there a constraint that restricts my generic method to numeric types?

Community
  • 1
  • 1
JJS
  • 6,431
  • 1
  • 54
  • 70
0

While this isn't exactly what you're looking for, you might find it useful in the interim to save a lot of typing:

Imports WindowsApplication2.Extensions

Public Module Extensions
    <Extension()>
    Public Function Add(A As Object, B As Object) As Object
        Dim numa, numb As Double
        Dim gooda As Boolean = Double.TryParse(A.ToString, numa)
        Dim goodb As Boolean = Double.TryParse(B.ToString, numb)
        Return numa + numb
    End Function
End Module

I declared 2 booleans in there in case anyone desires stronger error checking. Any object cast to string that can't be parsed to a double will be treated as 0. This way as well you can mix string, int, double, long, float, etc. I used double since this seems to encompass most if not all the other number types. Of course this can easily be changed.

simply cast the return to whichever type you need. Inellisense will catch this and prompt you for the right casting, if you have all your options turned on

    Dim a As String = "5.0"
    Dim b As Double = CDbl(a.Add(2)) 

I know that this will offend some sensibilities, but like I said it might be useful to some people, as a stopgap measure.

Here's an interesting article. It's written using C#, but you might find the concepts useful as well.

tinstaafl
  • 6,908
  • 2
  • 15
  • 22
  • Thanks for the suggestion. Unfortunately this way still includes two problems: #1 the extension method is applied to all types and #2 one always has to cast to the destination time. – jor Aug 08 '13 at 06:29
  • Yep it's not perfect, but will sure save a lot of typing :) To avoid confusion you could add a description in intellisense to remind yourself which Add method it is. – tinstaafl Aug 08 '13 at 13:18
0

Inspired by this answer the following seems to work:

<Extension()> _
Public Function Add(Of T As {IConvertible, IComparable, IComparable(Of T), 
                             IFormattable, IEquatable(Of T)}) _
                   (a As T, b As T) As Decimal
    Return a.ToDecimal(Nothing) + b.ToDecimal(Nothing)
End Function

The type definition restricts the extension method close to numeric types. (Not 100%, though, as you could still call this on Byte and Date which isn't desired.)

Examples:

Dim dec As Decimal = Add(CDec(12.34), CDec(34.56))
Dim sng As Single = CSng(Add(CSng(34.56), CSng(45.67)))
Dim dbl As Double = CDbl(Add(CDbl(123.34), CDbl(45.123)))
Dim int As Integer = CInt(Add(CInt(12), CInt(34)))
Dim lng As Long = CLng(Add(CLng(1200), CLng(3400)))

Having two generic types also allows using mixed numeric types:

<Extension()> _
Public Function Add(Of T As {IConvertible, IComparable, IComparable(Of T), 
                             IFormattable, IEquatable(Of T)},
                       U As {IConvertible, IComparable, IComparable(Of U), 
                             IFormattable, IEquatable(Of U)}) _
                   (a As T, b As U) As Decimal
    Return a.ToDecimal(Nothing) + b.ToDecimal(Nothing)
End Function

Example:

Dim mixed As Decimal = Add(CDec(12.34), CSng(23.45))

I know that this is still not ideal because there might be some loss when converting to decimal or back. Also this still requires casting the result of the function if you're not working with decimals. But it does avoid the need of duplicates in many cases.

Community
  • 1
  • 1
jor
  • 2,058
  • 2
  • 26
  • 46