Skip to content

[Bug] IEnum GetHashCode is case-sensitive while Equals is case-insensitive, violating hash/equality contract #1679

Description

@gcatanese

Context

All generated IEnum classes (standalone enums via modelEnum.mustache and inner TypeEnum classes via modelInnerEnum.mustache) implement GetHashCode using the default case-sensitive string hash:

public override int GetHashCode() => Value?.GetHashCode() ?? 0;

However, Equals and the ==/!= operators use StringComparison.OrdinalIgnoreCase:

public override bool Equals(object? obj) =>
    obj is Result other && string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);

This violates the fundamental hash/equality contract: objects that compare equal must produce the same hash code. As a result, "valid" and "VALID" are considered equal by Equals but produce different hash codes, causing incorrect behaviour when IEnum instances are used as dictionary keys or in hash sets.

Affected generated types include all standalone IEnum classes (e.g. Result, LimitStatus, Scope) and all inner TypeEnum classes (e.g. CheckoutThreeDS2Action.TypeEnum, CheckoutDelegatedAuthenticationAction.TypeEnum).

Proposed Change

Fix GetHashCode in both modelEnum.mustache and modelInnerEnum.mustache to use a case-insensitive comparer:

public override int GetHashCode() =>
    StringComparer.OrdinalIgnoreCase.GetHashCode(Value ?? string.Empty);

This matches the comparer used by Equals and handles null consistently.

Benefit

Restores the hash/equality contract, making IEnum instances safe to use as dictionary keys or in hash sets, and eliminates a latent source of hard-to-debug lookup failures.

Notes

Low-severity latent bug

  • Narrow trigger condition: It only manifests when IEnum instances are used as dictionary keys or in hash sets, AND the casing differs between two values that should be considered equal (e.g., "valid" vs "VALID"). In normal SDK usage, enum values come from the API with consistent casing, so mismatches are unlikely.
  • Hard to hit in practice: Users rarely use TypeEnum instances as dictionary keys. The typical usage pattern is comparison via ==, which works correctly.
  • When triggered, the effect is subtle: A hash set/dictionary lookup would silently fail to find a matching key, leading to hard-to-debug "missing entry" bugs -- not crashes or data corruption.

In summary: it's a correctness violation of the .NET hash/equality contract that should be fixed for hygiene, but it's unlikely to affect real-world users with the current SDK usage patterns.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions