Skip to content

Dictionary DeepClone() drops the source's key comparer, so a cloned case-insensitive dictionary stops finding keys #77

Closed

Description

@matt-edmondson

What's wrong

The HashSet<T> and SortedSet<T> overloads of DeepClone() both keep the source comparer (new(source.Select(DeepClone), source.Comparer)). The dictionary overloads do not:

// IDictionary overload
return source.ToDictionary(pair => DeepClone(pair.Key), pair => DeepClone(pair.Value));

The clone is always a Dictionary<TKey,TValue> with the default equality comparer, so a clone does not behave like its source:

var d = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase) { ["Key"] = 1 };
d.ContainsKey("key");                                          // True
((IDictionary<string, int>)d).DeepClone().ContainsKey("key");  // False

I verified this against the current main source. It fails in these cases:

  • A settings or lookup table keyed case-insensitively silently misses lookups after cloning.
  • If the cloned keys are IDeepCloneable objects that rely on a custom IEqualityComparer, two distinct source keys can collide, or equal ones can split. The clone can then throw ArgumentException: An item with the same key has already been added.
  • A SortedDictionary input comes back as an unordered Dictionary.

The overloads are also awkward to call. Dictionary<TKey,TValue> implements both IDictionary<,> and IReadOnlyDictionary<,>, so dict.DeepClone() fails to compile with CS0121: the call is ambiguous. The tests work around this with a cast (((IDictionary<string, SimpleObject?>)originalDict).DeepClone()).

Suggested fix

  • Add a concrete Dictionary<TKey, TValue> DeepClone<TKey, TValue>(this Dictionary<TKey, TValue> source) overload that passes source.Comparer to the new dictionary. Being the most specific match, it also resolves the CS0121 ambiguity for the common case.
  • In the interface-typed overloads, keep the comparer when the runtime type exposes one (Dictionary<,>, SortedDictionary<,>), and return the same kind of collection where practical.

Acceptance criteria: tests show that a clone of a comparer-bearing dictionary answers lookups the same way as its source, and that dict.DeepClone() compiles on a Dictionary<,> without a cast.

Activity

  1. matt-edmondson commented on Sep 26, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    Notes: Adding a concrete Dictionary<TKey,TValue> overload resolves both the comparer loss for the common case and the CS0121 ambiguity. Check that it doesn't create new ambiguity for callers that pass SortedDictionary or other IDictionary implementations.


    Generated by Claude Code

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

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions