You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
DeepCloneFrom / IEnumerable.DeepClone share dictionary values with the source when it is not an IDictionary (e.g. IReadOnlyDictionary, LINQ over a dictionary) #82
When the source of a clone is a sequence of KeyValuePair<TKey, TValue> but not statically an IDictionary<TKey, TValue>, overload resolution picks the element-wise overloads with T = KeyValuePair<TKey, TValue>:
A KeyValuePair<,> never implements IDeepCloneable, so the pair is copied as-is and the key and value objects are shared with the source. Nothing errors.
The same thing happens with any IEnumerable<KeyValuePair<,>> source, such as an IReadOnlyDictionary, ImmutableDictionary exposed through an interface, or a LINQ projection over a dictionary.
Why it matters
The caller asked for a deep clone and got one back without any error, but the values are the original instances. Mutating the "clone" then mutates the source. This is the kind of aliasing bug the library exists to prevent. The IDictionary overload of DeepCloneFrom does clone both key and value, so the result depends on the static type the caller happened to hold.
Suggested fix
Either of these works:
In the private helper, detect KeyValuePair<TKey, TValue> and rebuild the pair from DeepClone(pair.Key) and DeepClone(pair.Value).
What's wrong
When the source of a clone is a sequence of
KeyValuePair<TKey, TValue>but not statically anIDictionary<TKey, TValue>, overload resolution picks the element-wise overloads withT = KeyValuePair<TKey, TValue>:DeepCloneFrom<T>(this ICollection<T> dest, IEnumerable<T> source)(DeepClone/DeepCloneContainerExtensions.cs:36)DeepClone<T>(this IEnumerable<T> source)(DeepCloneContainerExtensions.cs:116)Both clone each element through the private helper at
DeepCloneContainerExtensions.cs:97:A
KeyValuePair<,>never implementsIDeepCloneable, so the pair is copied as-is and the key and value objects are shared with the source. Nothing errors.Repro
The same thing happens with any
IEnumerable<KeyValuePair<,>>source, such as anIReadOnlyDictionary,ImmutableDictionaryexposed through an interface, or a LINQ projection over a dictionary.Why it matters
The caller asked for a deep clone and got one back without any error, but the values are the original instances. Mutating the "clone" then mutates the source. This is the kind of aliasing bug the library exists to prevent. The
IDictionaryoverload ofDeepCloneFromdoes clone both key and value, so the result depends on the static type the caller happened to hold.Suggested fix
Either of these works:
KeyValuePair<TKey, TValue>and rebuild the pair fromDeepClone(pair.Key)andDeepClone(pair.Value).DeepCloneFrom<TKey, TValue>(this IDictionary<TKey, TValue> dest, IEnumerable<KeyValuePair<TKey, TValue>> source)andDeepClone<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> source). Check that these don't reintroduce the CS0121 ambiguity tracked in dictionary.DeepClone() fails to compile (CS0121 ambiguous call) for Dictionary, SortedDictionary and ConcurrentDictionary, although the XML doc example shows that call #81.Acceptance: the repro above returns
Falsein both cases, with a regression test for each ofIReadOnlyDictionaryand a LINQ-filtered source.