Skip to content

Make [PSSemVer] usable as a dictionary key by overriding GetHashCode #47

Description

Context and request

Observed behavior: [PSSemVer] overrides Equals([Object]) but does not override GetHashCode(), so two equal instances get different hash codes. Any hash-based lookup therefore misses.

Import-Module PSSemVer
$h = @{}
$h[[PSSemVer]'1.0.0'] = 'a'
$h[[PSSemVer]'1.0.0']   # returns nothing

([PSSemVer]'1.0.0').GetHashCode() -eq ([PSSemVer]'1.0.0').GetHashCode()   # False

@([PSSemVer]'1.0.0', [PSSemVer]'1.0.0') | Group-Object | Measure-Object | Select-Object -ExpandProperty Count   # 2, expected 1

Expected behavior: equal [PSSemVer] instances produce equal hash codes, so hashtables, Dictionary<PSSemVer,T>, HashSet, and Group-Object treat them as the same key.

Reproduction: the snippet above, on a clean session with PSSemVer imported.

Environment: PSSemVer 1.1.9, PowerShell 7.6.4, Windows. Not platform specific — the defect is in src/classes/public/PSSemVer.ps1.

Regression: no. The GetHashCode() override has never existed.

Workaround: key on $version.ToString() instead of on the object.

Acceptance criteria:

  • ([PSSemVer]$a).GetHashCode() -eq ([PSSemVer]$b).GetHashCode() whenever ([PSSemVer]$a).Equals([PSSemVer]$b) is $true.
  • A hashtable keyed by [PSSemVer] returns the stored value for an equal instance.
  • Existing comparison and equality tests still pass.

Technical decisions

The root cause is bounded to src/classes/public/PSSemVer.ps1: Equals([Object]$other) is overridden without the matching GetHashCode() override, which breaks the .NET contract that equal objects must have equal hash codes. The override should hash exactly the fields Equals compares, so the two stay consistent; if the build-metadata precedence question is resolved separately, GetHashCode() must be updated in the same change as Equals. Prefix is deliberately excluded from Equals today and should stay excluded from the hash.

The regression test is the hashtable round-trip plus a direct hash-code equality assertion in tests/PSSemVer.Tests.ps1.

Implementation plan

Add the failing regression tests first, then add a [int] GetHashCode() override that combines the same members Equals uses, then re-run the full Pester suite.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugBroken functionality that fails or creates an unwanted outcomePatchFixes bugs or adds small fixes to existing functionality

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions