考虑:
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.CreateExpression
,Expression.Add
),我只需要支持少数数字类型,因此不值得理解这种方法,并评估其表现。相反,我手工编写了我需要的几种低级方法,用于我需要的几种类型。
上述所有方法的所有源材料都在C#中;因为我正在加入VB dll,所以我决定最终使用VB解决方案,而不是引用外部DLL,以最大化JIT编译器内联所涉及的简单方法的概率。也许更了解内部人员的人会得出结论,他们可以以不干扰JIT的方式使用现有的C#dll。
注意:如果上述方法存在任何性能问题,那么任何提高绩效的建议也会很棒。此代码用于具有数百万个元素的数组(图像分析)。
(嵌入在大量VB代码中,这些代码大部分都不是时间关键的,让程序员的工作效率/易于更改一些复杂的自定义算法更为重要;到目前为止还没有值得隔离一个用C ++重写代码的部分代码,或者使用数字库。好的,后一点是值得商榷的,但不管有多少自定义的VB公式密集型代码,都与其他遗留的VB.Net深深地缠绕在一起。代码。)
最坏的情况是,可能必须使用T4生成不同版本的MyClass,每个数值类型一个,而不是如图所示使用Generics。但是,如果可能的话,我宁愿避免这种情况 - 实际应用程序中有很多类的代码。所有UShort都是32位的小内存,但现在还需要Integer,Single和Double版本。
答案 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 T
或As 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
变体适用于外部客户,不了解Num
或TNum
。
Num
变体可以在内部使用,以构建涉及多种方法的计算。
这里的观点是,如果最终结果是冗长方法的内部具有易于阅读的代码,即使对于复杂的公式,我们还有一个值得花费相当多的一次性努力的情况,同时保持良好的性能,并使用几种不同的数字类型。
使用运算符合并现有代码也更容易,只需更改声明即可使用Num
和TNum
,偶尔也需要使用Shared ReadOnly tn As TNum
字段直接访问INumeric方法。
到目前为止,解决方案比替代方案更好(对于我的标准),但我仍然希望在可能的情况下消除额外的“垃圾”。