Considerations in Using Operator Overloading
There are several issues that you need to keep in mind when using operator overloading, in order to avoid unanticipated behavior.
Inheritance
If the operand(s) of an overloaded operator do not match what is defined explicitly by the operator's declaration, the standard overload resolution rules apply in determining which overload to use, as well as how the result is applied. Consider the following example in Code Block 11:
' Code Block 11
Sub Main()
Dim Q As New B
Dim P As B = -Q ' This will fail
End Sub
Class A
Public Shared Operator -(value As A) As A
.
.
.
End Operator
End Class
Class B
Inherits A
End Class
Since no unary negation operator is defined for class B, the unary negation operator for calls A (from which B derives) will be used as would be expected from a normal overloaded function. However, after that code is run, the assignment will return a value of type A, and then attempt to assign it to a reference to the derived type B, so the assignment will fail with an "Invalid cast" runtime error.
Narrowing and Widening
One important thing to note is that any overload of CType that you create must be defined as Narrowing or Widening. In general, Widening should be used when there is no possibility of error in the cast, whereas Narrowing should be used whenever there is a possibility of the cast failing.
Let's consider two structures—structure Position holds a two-degree/direction pairs (one for latitude, one for longitude), whereas structure GPSCoordinate contains not only a Position object, but an Elevation value as well. It's clear that GPSCoordinate can hold any information that Position can, but not the reverse. You'd lose the elevation information. Therefore, we can describe the relation from Position to GPSCoordinate as Widening, and that from GPSCoordinate to Position as Narrowing. When casting between the two, we can define these relationships in the overloaded operators so that they can be enforced correctly, as given in Code Block 12:
' Code Block 12
Option Strict On
Module Module1
Sub Main()
Dim pos As New Position(47.5, CompassPoint.North, _
180.2, CompassPoint.West)
Dim gps As GPSCoordinate
gps = pos ' This is OK -- widening conversion
gps.m_Elevation = 5.5
pos = gps ' Uh, oh -- narrowing conversion! Error when option strict is on.
End Sub
Enum CompassPoint
North = 0
East = 0
South = 1
West = 1
End Enum
Structure DegreeLine
Public Property Degrees() As Double
Get
Return m_Degrees
End Get
Set(ByVal value As Double)
m_Degrees = value
End Set
End Property
Public Property Direction() As CompassPoint
Get
Return m_Direction
End Get
Set(ByVal value As CompassPoint)
m_Direction = value
End Set
End Property
Private m_Degrees As Double
Private m_Direction As CompassPoint
End Structure
Structure Position
Public Sub New(ByVal lat As DegreeLine, _
ByVal lon As DegreeLine)
Latitude = lat
Longitude = lon
End Sub
Public Sub New(ByVal latdeg As Double, _
ByVal latdir As CompassPoint, _
ByVal londeg As Double, _
ByVal londir As CompassPoint)
m_Latitude.Degrees = latdeg
m_Latitude.Direction = latdir
m_Longitude.Degrees = londeg
m_Longitude.Direction = londir
End Sub
Public Property Latitude() As DegreeLine
Get
Return m_Latitude
End Get
Set(ByVal value As DegreeLine)
m_Latitude.Degrees = value.Degrees
m_Latitude.Direction = value.Direction
End Set
End Property
Public Property Longitude() As DegreeLine
Get
Return m_Longitude
End Get
Set(ByVal value As DegreeLine)
m_Longitude.Degrees = value.Degrees
m_Longitude.Direction = value.Direction
End Set
End Property
Private m_Latitude As DegreeLine
Private m_Longitude As DegreeLine
End Structure
Structure GPSCoordinate
Public Sub New(ByVal positionValue As Position, _
ByVal elevationValue As Double)
m_Elevation = elevationValue
m_Position.Latitude = positionValue.Latitude
m_Position.Longitude = positionValue.Longitude
End Sub
Public Overloads Shared _
Narrowing Operator CType( _
ByVal value As GPSCoordinate) As Position
If value.m_Elevation <> 0.0 Then
Throw New ArgumentException() _
' Elevation information would be lost!
End If
Return New Position(value.m_Position.Latitude, value.m_Position.Longitude)
End Operator
Public Overloads Shared _
Widening Operator CType( _
ByVal value As Position) As GPSCoordinate
Return New GPSCoordinate(value, 0.0)
End Operator
Public m_Position As Position
Public m_Elevation As Double
End Structure
End Module
Casting from GPSCoordinate to Position will throw an exception at runtime if the elevation portion of the GPSCoordinate is non-zero. To make matters worse, when Option Strict is turned on (which is a recommended practice), the line of code that attempts the cast will be in error, due to the "Narrowing" qualifier in the relevant cast operator, unless CType() is explicitly used. Assigning a Position type to a GPSCoordinate type, on the other hand, will always work regardless of whether or not Option Strict is on, since the cast in that direction is defined as Widening. In this case, we just define the missing elevation to be zero.
Note that, in this case, we've defined the overloaded operators on the GPSCoordinate structure. We could just as easily have defined them on the Position structure. As long as Position was used in the overloaded operators either as a return value or an argument, this would be perfectly legal. However, the Position structure does not otherwise depend on the GPSCoordinate structure, whereas, even without the overloaded operators, GPSCoordinate depends on the Position structure. Therefore, it makes the most sense to have the operators defined in the GPSCoordinate structure. Otherwise, if we decided to move just the position structure to a library, we'd be in the awkward position of the Position code and the GPSCoordinate code having to reference each other, which is not a good coding practice and defeats the whole purpose of having a library.
Pair Operations
The following pairs of operators are linked. if you define one of them, the other must be defined as well: