Value type overrides Equals but does not overload the = operator
ID |
vbnet.correctness.override_equals_on_overloading |
Severity |
low |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Type Design |
Language |
VB.NET |
Description
Reports a Structure that defines value equality by overriding Equals but declares no =
operator anywhere in its body. Any Operator = declaration is accepted, whatever its
signature. Structures that do not override Equals at all are outside the scope of the rule -
it reports the half-finished value type, not the one that never claimed value semantics - and
classes are not reported.
Rationale
A structure that overrides Equals is announcing that two instances holding the same data are
the same value, which is how Integer, Date and Decimal behave. The operators are what
make that usable: Visual Basic does not synthesize = for a structure, so If a = b Then on
the type is a compile error, and the only way to ask the question is a.Equals(b). Call sites
then read differently from every other value in the codebase, and the comparison that a
developer writes by reflex is the one that does not work. The worse case is the code that does
compile. Once an instance is held in an Object, a collection of Object, or a late-bound
expression under Option Strict Off, a comparison no longer binds to the structure’s own
equality at all, so it silently resolves to something other than the Equals that was
carefully written - two values that Equals reports as equal can compare unequal, with no
warning at either the definition or the comparison. Supplying = without <> produces a
third variant of the same problem, where a = b and Not (a <> b) disagree.
The following code illustrates the pattern detected by this rule:
Namespace Acme.Measures
' FLAGGED: Value type overrides Equals but does not overload the = operator
Public Structure Weight
Public Grams As Integer
Public Overrides Function Equals(obj As Object) As Boolean
If Not (TypeOf obj Is Weight) Then Return False
Return Grams = DirectCast(obj, Weight).Grams
End Function
Remediation
Declare Public Shared Operator = and the matching Public Shared Operator <> - Visual Basic
requires the pair - and have both delegate to Equals so there is exactly one definition of
equality for the type. Implement IEquatable(Of T) at the same time to give callers and
generic collections a typed, non-boxing comparison, and keep GetHashCode derived from the
same members.
' Before: equality is reachable only as weight.Equals(other)
Public Structure Weight
Public Grams As Integer
Public Overrides Function Equals(obj As Object) As Boolean
If Not (TypeOf obj Is Weight) Then Return False
Return Grams = DirectCast(obj, Weight).Grams
End Function
End Structure
' After
Public Shared Operator =(left As Weight, right As Weight) As Boolean
Return left.Equals(right)
End Operator
Public Shared Operator <>(left As Weight, right As Weight) As Boolean
Return Not left.Equals(right)
End Operator