有没有办法使用“>”运算符在“T作为IC Comparable(Of T)”,而不是“a.GreaterThan(b)”,或简化我的包装线的方法?

时间:2014-10-23 22:25:55

标签: vb.net generics icomparable

考虑:

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

通过定义:

,可以使其更具可读性
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

所以测试成为:

    If a.GreaterThan(b) Then

这是可以接受的,但作为更复杂的表达式的一部分,能够定义运算符会很棒,所以可以说

    If a > b Then

但MyClass中的以下定义:

    Public Shared Operator >(a As T, b As T) As Boolean
        Return (a.CompareTo(b) > 0)
    End Operator

产生编译时错误&#34;此二元运算符的至少一个参数必须是包含类型...&#34;

有没有其他方法可以实现这一目标?


替代方案我很快(但不是100%满意):

与此方法类似:

http://www.codeproject.com/Articles/8531/Using-generics-for-calculations

为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

但是必须将Num() - Dim value As New Num(Of T, TNum)(valueT)中的一个值包装起来 - 以便让>进行编译并不比我已经工作的更方便:

Public Sub MySub(valueT As T)
     If valueT.GreaterThan(Me.MaxElement) Then
        ...
    End If
End Sub

因此,另一种解决方案是使这条线更优雅:

Dim value As New Num(Of T, TNum)(valueT)

使Num工作所涉及的类型受到上述参考的启发,并采用这种方法:

https://stackoverflow.com/a/4834066/199364

指的是Policy.I.cs和Policy.INumeric.cs:

https://citylizard.codeplex.com/

以下是关键类型的精简草图:

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

最后可以定义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

使用MyClass:

Public Shared Sub Test()
    Dim v As New MyClass(Of Integer, Numeric)()
    ...
    v.MySub(99)
End Sub

我想消除或简化的一行是:

Dim value As New Num(Of T, TNum)(valueT)

此行仅在那里,>可以正常工作。通过将>的其中一个参数设为Num()类型,T类型的另一个参数会自动加宽为Num(),然后>找到了。

有没有办法更改这些定义,以便上述行更简单,或者不需要?

请注意,我不希望要求PARAMETER到MySub为Num() - 这会将负担转移到不应该关注此实现细节的代码上 - 它应该与T合作。同样,>中使用的其他值 - 此处MaxElement - 应该是T而不是Num()。至少在某些情况下;这是一个简化的例子。


另一个参考:通用数字的另一个起点是使用Linq表达式的Mark Gravell的通用运算符,它是MiscUtil的一部分:

https://jonskeet.uk/csharp/miscutil/usage/genericoperators.html

Operator.cs中看到的内部代码对我来说并不熟悉(ExpressionUtil.CreateExpressionExpression.Add),我只需要支持少数数字类型,因此不值得理解这种方法,并评估其表现。相反,我手工编写了我需要的几种低级方法,用于我需要的几种类型。

上述所有方法的所有源材料都在C#中;因为我正在加入VB dll,所以我决定最终使用VB解决方案,而不是引用外部DLL,以最大化JIT编译器内联所涉及的简单方法的概率。也许更了解内部人员的人会得出结论,他们可以以不干扰JIT的方式使用现有的C#dll。


注意:如果上述方法存在任何性能问题,那么任何提高绩效的建议也会很棒。此代码用于具有数百万个元素的数组(图像分析)。

(嵌入在大量VB代码中,这些代码大部分都不是时间关键的,让程序员的工作效率/易于更改一些复杂的自定义算法更为重要;到目前为止还没有值得隔离一个用C ++重写代码的部分代码,或者使用数字库。好的,后一点是值得商榷的,但不管有多少自定义的VB公式密集型代码,都与其他遗留的VB.Net深深地缠绕在一起。代码。)

最坏的情况是,可能必须使用T4生成不同版本的MyClass,每个数值类型一个,而不是如图所示使用Generics。但是,如果可能的话,我宁愿避免这种情况 - 实际应用程序中有很多类的代码。所有UShort都是32位的小内存,但现在还需要Integer,Single和Double版本。

1 个答案:

答案 0 :(得分:0)

简化包装的一种方法:

向MyClass(或具有相同T / TNum签名的继承基类)添加方法,以使转换更紧凑:

Public Shared Function AsNum(valueT As T) As Num(Of T, TNum)
    Return New Num(Of T, TNum)(valueT)
End Function

然后可以更紧凑地包装参数valueT

If AsNum(valueT) > Me.MaxElement Then

或者,如果将重复使用,并进行类型推断:

Dim value = AsNum(valueT)
If value > Me.MaxElement Then

限制:

我有一些继承自现有BaseClass(Of T)的类,我希望避免更改BaseClass以使用Num()包装器。所以他们无法通过继承获得AsNum。这些类中的每一个都需要自己的AsNum方法副本。

AsNum不能放在适用于所有类的单独模块中,即Public Function AsNum (Of T As {New, IComparable(Of T)}, TNum As {New, INumeric(Of T)}) (valueT As T) As Num(Of T, TNum),因为除了类TNum之外无法推断Of .. TNum的类型{1}}。


对于+中的算术运算符(*Structure Num),是否返回As TAs Num(Of T, TNum)存在一些疑问。目前我正在返回T,因为这似乎更有效,因为我通常将结果存储到T。但是,这在复杂的表达式中不能很好地工作,因为我不得不将中间值包装回Num()

Structure Num
    ...
    Public Shared Operator +(a As Num(Of T, TNum), b As Num(Of T, TNum)) As T
        Return tn.Add(a.value, b.value)
    End Operator
End Structure
    ...
Class MyClass(Of T As {New, IComparable(Of T)}, TNum As {New, INumeric(Of T)})
    ...
    Public Shared Function MyFunc(a As T, b As T) As T
        Return AsNum(AsNum(a) * a) + AsNum(b) * b
    End Function
End Class

VS

Structure Num(Of T As {New, IComparable(Of T)}, TNum As {New, INumeric(Of T)})
    ...
    ' NOTE: The widening operator automatically converts the "T" from "tn.Add" to a "Num()".
    Public Shared Operator +(a As Num(Of T, TNum), b As Num(Of T, TNum)) As Num(Of T, TNum)
        Return tn.Add(a.value, b.value)
    End Operator
End Structure
    ...
Class MyClass(Of T As {New, IComparable(Of T)}, TNum As {New, INumeric(Of T)})
    ...
    Public Shared Function MyFunc(a As T, b As T) As T
        ' Each sub-expression needs at least one "Num()",
        ' but at least we no longer need a third "AsNum",
        ' for the "+" to work.
        Return AsNum(a) * a + AsNum(b) * b
    End Function
End Class

使用上面的第二个表单,它返回Num():对于一些复杂的方法,最干净的解决方案可能是有两个变体, 使用T参数化变体调用Num()参数化变体:

Public Shared Function MyFunc(a As T, b As T) As T
    Return MyFunc(AsNum(a), AsNum(b))
End Function
Private Shared Function MyFunc(a As Num(Of T, TNum), b As Num(Of T, TNum)) As Num(Of T, TNum)
    Return a * a + b * b
End Function

T变体适用于外部客户,不了解NumTNumNum变体可以在内部使用,以构建涉及多种方法的计算。


这里的观点是,如果最终结果是冗长方法的内部具有易于阅读的代码,即使对于复杂的公式,我们还有一个值得花费相当多的一次性努力的情况,同时保持良好的性能,并使用几种不同的数字类型。

使用运算符合并现有代码也更容易,只需更改声明即可使用NumTNum,偶尔也需要使用Shared ReadOnly tn As TNum字段直接访问INumeric方法。

到目前为止,解决方案比替代方案更好(对于我的标准),但我仍然希望在可能的情况下消除额外的“垃圾”。