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

Configuration

This detector does not need any configuration.