jor
jor

Reputation: 2157

Generic Method for Numeric Types

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?

Upvotes: 3

Views: 2248

Answers (4)

JJS
JJS

Reputation: 6668

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?

Upvotes: 1

jor
jor

Reputation: 2157

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.

Upvotes: 0

tinstaafl
tinstaafl

Reputation: 6948

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.

Upvotes: 0

Karl Anderson
Karl Anderson

Reputation: 34846

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.

Upvotes: 2

Related Questions