Consider:
MyClass(Of T As {New, IComparable(Of T)})
Sub MySub(a As T, b As T)
If a.CompareTo(b) > 0 Then
....
End If
End Sub
End Class
This can be made more readable by defining:
Public Module MyModule
<System.Runtime.CompilerServices.Extension()> _
Public Function GreaterThan(Of T As IComparable(Of T))(a As T, b As T) As Boolean
Return (a.CompareTo(b) > 0)
End Function
End Module
So the test becomes:
If a.GreaterThan(b) Then
This is acceptable, but as part of more complicated expressions, it would be great to be able to define an operator, so could instead say
If a > b Then
However the following definition in MyClass:
Public Shared Operator >(a As T, b As T) As Boolean
Return (a.CompareTo(b) > 0)
End Operator
yields compile-time error "At least one parameter of this binary operator must be of the containing type..."
Is there any alternative way to make this possible?
THE ALTERNATIVE I HAVE SO FAR (but isn't 100% satisfactory):
Similar to this approach:
http://www.codeproject.com/Articles/8531/Using-generics-for-calculations
which creates a wrapper structure for the T - a structure containing a single field - and then defines operators on that wrapper. This makes it possible to do code that looks like this:
Public Sub MySub(valueT As T)
Dim value As New Num(Of T, TNum)(valueT)
If value > Me.MaxElement Then
...
End If
End Sub
But having to wrap one of the values inside of Num()
-- Dim value As New Num(Of T, TNum)(valueT)
-- in order to get the >
to compile is no more convenient than doing what I already have working:
Public Sub MySub(valueT As T)
If valueT.GreaterThan(Me.MaxElement) Then
...
End If
End Sub
So an alternative solution would be some way to make this line more elegant:
Dim value As New Num(Of T, TNum)(valueT)
The types involved in making Num
work are inspired by the above reference, and by this approach:
https://stackoverflow.com/a/4834066/199364
which refers to Policy.I.cs and Policy.INumeric.cs within this:
https://citylizard.codeplex.com/
Here is a stripped down sketch of key types:
Public Interface INumeric(Of T As {New, IComparable(Of T)})
Function Zero() As T
...
Function Add(a As T, b As T) As T
...
Function GreaterThan(a As T, b As T) As Boolean
...
End Interface
Public Structure Numeric
Implements INumeric(Of Integer),
INumeric(Of Single), ...
...
Public Function GreaterThan(ByVal a As Integer, ByVal b As Integer) As Boolean Implements INumeric(Of Integer).GreaterThan
Return (a > b)
End Function
...
Public Function GreaterThan(ByVal a As Single, ByVal b As Single) As Boolean Implements INumeric(Of Single).GreaterThan
Return (a > b)
End Function
...
End Structure
' Wrapper, to simplify use of Structure Numeric.
Public Structure Num(Of T As {New, IComparable(Of T)}, TNum As {New, INumeric(Of T)})
Public Shared ReadOnly tn As TNum = New TNum()
Private ReadOnly value As T
Public Sub New(a As T)
Me.value = a
End Sub
' implicitly convert "T" to "Num(Of T, TNum)"; e.g. "11" to "Num(Of Integer, ..) with value 11".
Public Overloads Shared Widening Operator CType(a As T) As Num(Of T, TNum)
Return New Num(Of T, TNum)(a)
End Operator
' Implicitly convert "Num(Of T, TNum)" back to "T"; e.g. retrieve value "11".
Public Overloads Shared Widening Operator CType(a As Num(Of T, TNum)) As T
Return a.value
End Operator
...
Public Shared Operator <(a As Num(Of T, TNum), b As Num(Of T, TNum)) As Boolean
Return tn.LessThan(a.value, b.value)
End Operator
...
End Structure
and finally one can define MyClass:
Class MyClass(Of T As {New, IComparable(Of T)}, TNum As {New, INumeric(Of T)})
Public Shared ReadOnly tn As TNum = New TNum()
Public MaxElement As T = ...
Public Sub MySub(valueT As T)
Dim value As New Num(Of T, TNum)(valueT)
If value > Me.MaxElement Then
...
End If
End Sub
End Class
usage of MyClass:
Public Shared Sub Test()
Dim v As New MyClass(Of Integer, Numeric)()
...
v.MySub(99)
End Sub
The line I would like to eliminate or simplify is:
Dim value As New Num(Of T, TNum)(valueT)
This line is only there so that the >
can work. By making one of the parameters to >
be type Num()
, the other parameter of type T
gets automatically widened to also be Num()
, and then the >
is found.
Is there any way to change these definitions so that the above line would be simpler, or not needed?
Note that I don't want to require the PARAMETER to MySub to be Num()
-- that would push the burden to code that shouldn't be concerned with this implementation detail -- it should work with T
. Likewise, the other value used in >
-- here, MaxElement
-- should be of type T
not Num()
. At least in some cases; this is a simplified example.
Another Reference: An alternative starting point for generic numerics would have been Mark Gravell's Generic Operators using Linq Expressions, which is part of MiscUtil:
https://jonskeet.uk/csharp/miscutil/usage/genericoperators.html
But the internal code seen in Operator.cs
is unfamiliar to me (ExpressionUtil.CreateExpression
, Expression.Add
), and I only need to support a handful of numeric types, so it was not worth understanding that approach, and evaluating its performance. Instead I hand-coded the few low-level methods I needed, for the few types I needed.
All the source materials for all of the approaches above are in C#; since I was incorporating into a VB dll, I was determined to end up with a VB solution, rather than reference an external DLL, to maximize the probability that JIT compiler will inline the simple methods that are involved. Perhaps someone more knowledgeable about the internals would have concluded they could use one of the existing C# dlls in a way that did not interfere with JIT.
NOTE: If there are any performance issues with the approach above, any performance-improving suggestions would also be great. This code is used with arrays having millions of elements (image analysis).
(Embedded in a large body of VB code that is mostly not time-critical, and it was more important to have programmer productivity / ease of changing some complicated custom algorithms; it so far hasn't been worth isolating a portion of the code for re-writing in C++, or using a numeric library. OK, that latter point is debatable, but regardless there would be a lot of custom VB formula-intensive code, that is deeply entwined with other legacy VB.Net code.)
Worst case, might have to use T4 to generate different versions of MyClass, one per numeric value type, instead of using Generics as shown. But I'd rather avoid that if possible -- there is a lot of code, over a number of classes, in the actual application. Was all UShort for small memory in 32-bit, but now also need Integer, Single, and Double versions.