Skip to content

dictionary.DeepClone() fails to compile (CS0121 ambiguous call) for Dictionary, SortedDictionary and ConcurrentDictionary, although the XML doc example shows that call #81

Description

@matt-edmondson

What's wrong

DeepCloneContainerExtensions has two dictionary extensions:

  • DeepClone(this IDictionary<TKey,TValue>) (line 137)
  • DeepClone(this IReadOnlyDictionary<TKey,TValue>) (line 164)

Dictionary<,>, SortedDictionary<,>, ConcurrentDictionary<,> and ReadOnlyDictionary<,> all implement both interfaces. For these types neither overload is better than the other, so the call is ambiguous and won't compile:

var d = new Dictionary<string,int>();
var c = d.DeepClone();
// error CS0121: The call is ambiguous between
//   DeepCloneContainerExtensions.DeepClone<TKey,TValue>(IDictionary<TKey,TValue>) and
//   DeepCloneContainerExtensions.DeepClone<TKey,TValue>(IReadOnlyDictionary<TKey,TValue>)

The XML doc example on the IDictionary overload (around line 133) shows this exact call: var clonedDict = originalDict.DeepClone();. The tests already work around the problem with explicit casts:

  • DeepClone.Test/DeepCloneTests.cs:317: "Cast to IDictionary to resolve ambiguity"
  • DeepClone.Test/SpecializedCollectionTests.cs:264: "explicit cast to IDictionary to resolve ambiguity"

In practice, anyone deep-cloning the most common dictionary type has to cast first.

Reproduction

Add var c = new Dictionary<string,int>().DeepClone(); to any file in a project that references the library. dotnet build fails with CS0121, naming both overloads.

Suggested fix

Add overloads for the concrete types, in the same style as the existing HashSet<T>, SortedSet<T> and Stack<T> overloads. The compiler prefers them over the interface overloads. They can also return the concrete type and keep the comparer, which fixes #77 for these types at the same time:

public static Dictionary<TKey,TValue> DeepClone<TKey,TValue>(this Dictionary<TKey,TValue> source) where TKey : notnull
{
    Ensure.NotNull(source);
    return source.ToDictionary(p => DeepClone(p.Key), p => DeepClone(p.Value), source.Comparer);
}

Add matching overloads for SortedDictionary<,> (keep Comparer) and ConcurrentDictionary<,>.

Acceptance: new Dictionary<string,int>().DeepClone() compiles and returns an independent Dictionary with the same comparer, and the cast workarounds in the tests can be removed.

Activity

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

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions