Skip to content

[Performance] PatternMatchComparer.GetHashCode breaks Dictionary invariant — O(1) lookups degrade to O(n) #70

Description

@mfogliatto

Description

PatternMatchComparer implements IEqualityComparer<string> but its GetHashCode method uses the default string.GetHashCode(). This violates the IEqualityComparer contract: when Equals(x, y) returns true, GetHashCode(x) must equal GetHashCode(y).

Specifically, PatternMatchComparer.Equals("*", "SomeAssembly") returns true, but "*".GetHashCode() != "SomeAssembly".GetHashCode(). This means any dictionary using this comparer cannot perform correct O(1) lookups for wildcard patterns.

Affected Files

  • src/ReferenceCop/Comparers/PatternMatchComparer.csGetHashCode() (line 12)
  • src/ReferenceCop/Detectors/AssemblyNameViolationDetector.csrules dictionary uses this comparer (line 8)

Impact

  • Broken invariant: The Dictionary<string, Rule> in AssemblyNameViolationDetector uses PatternMatchComparer as its equality comparer. Dictionary lookups via TryGetValue or indexer for wildcard keys will silently fail to find matches because hash codes differ.
  • Hidden by current usage: The current GetViolationsFrom() iterates this.rules via foreach (treating the dictionary as IEnumerable<KeyValuePair>), which bypasses hash-based lookup entirely — masking the bug. But it also means the Dictionary provides zero performance benefit over a List<KeyValuePair>.
  • The experimental path is affected: GetViolationsFromExperimental uses exactMatchRules.TryGetValue() which works for exact matches (correct hash codes), but the original rules dictionary remains broken for any consumers that might try to look up by key.

Suggested Fix

Since PatternMatchComparer supports wildcards, it cannot produce consistent hash codes for all equal pairs. Two options:

  1. Replace the Dictionary with a List for rule storage (since it is only iterated, never looked up by key):
private readonly List<KeyValuePair<string, ReferenceCopConfig.Rule>> rules;

This makes the actual data structure match how it is used and avoids the misleading Dictionary type.

  1. If dictionary lookup is needed, use a constant hash code (trades O(1) for correctness):
public int GetHashCode(string obj) => 0; // Forces bucket collision, but maintains contract

This is correct but makes the dictionary degenerate to O(n). Option 1 is preferred since the dictionary is never used as a dictionary.

Metadata

Metadata

Assignees

No one assigned

    Labels

    performancePerformance optimization

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions