Skip to content

Sort a chord's notes ordinally, so its hash doesn't change with the culture [patch] - #227

Open
matt-edmondson wants to merge 1 commit into
mainfrom
fix/225-chord-ordinal-order
Open

matt-edmondson wants to merge 1 commit into
mainfrom
fix/225-chord-ordinal-order

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #225

What was wrong

Chord sorted its notes with the current culture in two places: the constructor (OrderBy(n => n.Key.ToString())) and GetHashCode, which sorted again on every call. Some cultures order letters differently: et-EE sorts Z before T, and lt-LT sorts Y before J. So the same unchanged chord hashed differently on threads with different cultures, and HashSet<Chord> / Dictionary<Chord, …> lookups missed chords that compare equal. The stored note order, which ToString() and the saved file use, also depended on the culture of whichever thread built the chord.

Change

  • The constructor sorts with StringComparer.Ordinal.
  • GetHashCode hashes _notes in that stored order and no longer sorts on each call.
  • Keybinding.Test sets <InvariantGlobalization>false</InvariantGlobalization>, because ktsu.Sdk turns invariant mode on and every culture would then sort like the invariant one. FuzzySearch, TextFilter and Semantics do the same in their test projects. Turning invariant mode off enables CA1308 on an existing test that lowercases an alias on purpose, so that test method gets a SuppressMessage with a justification.

Tests

  • New ChordCultureInvarianceTests. It builds Ctrl+T+Z and Ctrl+J+Y under en-US, then under et-EE / lt-LT checks:
    • the hash code is unchanged
    • HashSet and Dictionary lookups still find the chord
    • the stored note order and ToString() are the same in both cultures
  • With the MusicalTypes.cs change reverted, all 4 new cases fail. With it, the full suite passes: 385/385 on Linux / .NET 10. Keybinding.sln builds with 0 warnings.

Generated by Claude Code

…ulture [patch]

Chord sorted its notes with the current culture in the constructor and again
in GetHashCode. Under et-EE (Z before T) or lt-LT (Y before J) the same chord
hashed differently, so HashSet<Chord> and Dictionary<Chord, ...> lookups
missed equal chords, and the stored/saved note order depended on the thread
that built the chord.

The constructor now sorts with StringComparer.Ordinal and GetHashCode hashes
the stored order. The test project turns off invariant globalization so the
culture tests see real culture data.

Fixes #225

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW3JF2xHwwm6qrw3Bh7
@sonarqubecloud

sonarqubecloud Bot commented Nov 6, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A Chord's hash code changes with the thread's culture, so a HashSet<Chord> or Dictionary<Chord, …> stops finding "Ctrl+T+Z" under et-EE

2 participants