From 771f3b2154968193396a1c860226b91efecb19f2 Mon Sep 17 00:00:00 2001 From: Sir Phillip Tubell Date: Wed, 17 Jun 2026 12:49:41 -0400 Subject: [PATCH 1/3] fix: GetHashCode in generated string comparers now hashes the value, not the comparer __StringEqualityComparer.GetHashCode was calling _comparer.GetHashCode() with no argument, returning object.GetHashCode() on the comparer instance itself. Every value object therefore got the same hash code when using the generated Comparers, making hash-based collections degenerate to O(n). Fixed to pass obj._value to the comparer. Added regression tests to each StringComparisons test class asserting that two unequal values produce different hash codes. --- src/Vogen/GenerateCodeForStringComparers.cs | 4 ++-- tests/ConsumerTests/StringComparisons/ForClasses.cs | 12 ++++++++++++ .../StringComparisons/ForRecordClasses.cs | 12 ++++++++++++ .../StringComparisons/ForRecordStructs.cs | 12 ++++++++++++ tests/ConsumerTests/StringComparisons/ForStructs.cs | 12 ++++++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/Vogen/GenerateCodeForStringComparers.cs b/src/Vogen/GenerateCodeForStringComparers.cs index 0a57885290f..cb953992b9b 100644 --- a/src/Vogen/GenerateCodeForStringComparers.cs +++ b/src/Vogen/GenerateCodeForStringComparers.cs @@ -35,9 +35,9 @@ public bool Equals({{tds.Identifier}} x, {{tds.Identifier}} y) return _comparer.Equals(x._value, y._value); } - public int GetHashCode({{tds.Identifier}} obj) + public int GetHashCode({{tds.Identifier}} obj) { - return _comparer.GetHashCode(); + return _comparer.GetHashCode(obj._value); } } diff --git a/tests/ConsumerTests/StringComparisons/ForClasses.cs b/tests/ConsumerTests/StringComparisons/ForClasses.cs index 106cf2b3bbb..108bc273ddf 100644 --- a/tests/ConsumerTests/StringComparisons/ForClasses.cs +++ b/tests/ConsumerTests/StringComparisons/ForClasses.cs @@ -55,6 +55,18 @@ public void OrdinalIgnoreCase_Generic() (left == right).Should().BeFalse(); } + [Fact] + public void OrdinalIgnoreCase_GetHashCode_reflects_underlying_value() + { + var comparer = StringVo_Class_Generic.Comparers.OrdinalIgnoreCase; + + var a = StringVo_Class_Generic.From("abc"); + var b = StringVo_Class_Generic.From("xyz"); + + comparer.GetHashCode(a).Should().Be(comparer.GetHashCode(StringVo_Class_Generic.From("abc"))); + comparer.GetHashCode(a).Should().NotBe(comparer.GetHashCode(b)); + } + [Fact] public void OrdinalIgnoreCase_in_a_dictionary() { diff --git a/tests/ConsumerTests/StringComparisons/ForRecordClasses.cs b/tests/ConsumerTests/StringComparisons/ForRecordClasses.cs index 6786b3e5c72..fc7dd826818 100644 --- a/tests/ConsumerTests/StringComparisons/ForRecordClasses.cs +++ b/tests/ConsumerTests/StringComparisons/ForRecordClasses.cs @@ -55,6 +55,18 @@ public void OrdinalIgnoreCase_Generic() (left == right).Should().BeFalse(); } + [Fact] + public void OrdinalIgnoreCase_GetHashCode_reflects_underlying_value() + { + var comparer = StringVo_RecordClass_Generic.Comparers.OrdinalIgnoreCase; + + var a = StringVo_RecordClass_Generic.From("abc"); + var b = StringVo_RecordClass_Generic.From("xyz"); + + comparer.GetHashCode(a).Should().Be(comparer.GetHashCode(StringVo_RecordClass_Generic.From("abc"))); + comparer.GetHashCode(a).Should().NotBe(comparer.GetHashCode(b)); + } + [Fact] public void OrdinalIgnoreCase_in_a_dictionary() { diff --git a/tests/ConsumerTests/StringComparisons/ForRecordStructs.cs b/tests/ConsumerTests/StringComparisons/ForRecordStructs.cs index d949613fc44..c09f5e3dcda 100644 --- a/tests/ConsumerTests/StringComparisons/ForRecordStructs.cs +++ b/tests/ConsumerTests/StringComparisons/ForRecordStructs.cs @@ -58,6 +58,18 @@ public void OrdinalIgnoreCase_Generic() (left == right).Should().BeFalse(); } + [Fact] + public void OrdinalIgnoreCase_GetHashCode_reflects_underlying_value() + { + var comparer = StringVo_RecordStruct_Generic.Comparers.OrdinalIgnoreCase; + + var a = StringVo_RecordStruct_Generic.From("abc"); + var b = StringVo_RecordStruct_Generic.From("xyz"); + + comparer.GetHashCode(a).Should().Be(comparer.GetHashCode(StringVo_RecordStruct_Generic.From("abc"))); + comparer.GetHashCode(a).Should().NotBe(comparer.GetHashCode(b)); + } + [Fact] public void OrdinalIgnoreCase_in_a_dictionary() { diff --git a/tests/ConsumerTests/StringComparisons/ForStructs.cs b/tests/ConsumerTests/StringComparisons/ForStructs.cs index 0d29d5debc6..71a86182408 100644 --- a/tests/ConsumerTests/StringComparisons/ForStructs.cs +++ b/tests/ConsumerTests/StringComparisons/ForStructs.cs @@ -71,6 +71,18 @@ public void OrdinalIgnoreCase_Generic() (left == right).Should().BeFalse(); } + [Fact] + public void OrdinalIgnoreCase_GetHashCode_reflects_underlying_value() + { + var comparer = StringVo_Struct_Generic.Comparers.OrdinalIgnoreCase; + + var a = StringVo_Struct_Generic.From("abc"); + var b = StringVo_Struct_Generic.From("xyz"); + + comparer.GetHashCode(a).Should().Be(comparer.GetHashCode(StringVo_Struct_Generic.From("abc"))); + comparer.GetHashCode(a).Should().NotBe(comparer.GetHashCode(b)); + } + [Fact] public void OrdinalIgnoreCase_in_a_dictionary() { From 64a6d9090794a2956cb7efea36057cd424e74c3c Mon Sep 17 00:00:00 2001 From: Sir Phillip Tubell Date: Mon, 29 Jun 2026 08:45:48 -0400 Subject: [PATCH 2/3] test: updated snapshot tests --- ...parisonGenerationTests.Generates_when_specified.verified.txt | 2 +- ...parisonGenerationTests.Generates_when_specified.verified.txt | 2 +- ...parisonGenerationTests.Generates_when_specified.verified.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_when_specified.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_when_specified.verified.txt index 7c9943c0d1a..5662b7abb80 100644 --- a/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_when_specified.verified.txt +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v4.8/StringComparisonGenerationTests.Generates_when_specified.verified.txt @@ -208,7 +208,7 @@ private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; public int GetHashCode(StringThing obj) { - return _comparer.GetHashCode(); + return _comparer.GetHashCode(obj._value); } } diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt index 351990388f5..26f2975c54d 100644 --- a/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v8.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt @@ -208,7 +208,7 @@ private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; public int GetHashCode(StringThing obj) { - return _comparer.GetHashCode(); + return _comparer.GetHashCode(obj._value); } } diff --git a/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt b/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt index 351990388f5..26f2975c54d 100644 --- a/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt +++ b/tests/SnapshotTests/StringComparison/snapshots/snap-v9.0/StringComparisonGenerationTests.Generates_when_specified.verified.txt @@ -208,7 +208,7 @@ private readonly global::System.Diagnostics.StackTrace _stackTrace = null!; public int GetHashCode(StringThing obj) { - return _comparer.GetHashCode(); + return _comparer.GetHashCode(obj._value); } } From 2e83d001832895a608a14fea0d2a9fa49771e2ee Mon Sep 17 00:00:00 2001 From: Sir Phillip Tubell Date: Mon, 29 Jun 2026 08:51:31 -0400 Subject: [PATCH 3/3] revert: undo unintended commit --- nuget.config | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/nuget.config b/nuget.config index 465cf522eb1..af46f423e7d 100644 --- a/nuget.config +++ b/nuget.config @@ -6,15 +6,4 @@ - - - - - - - - - - - \ No newline at end of file