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
{{ message }}
Repository navigation
IEnumerable<T>.DeepClone() returns a lazy view: edits to the cloned items are lost, and the clone changes whenever the source changes #80
Select runs lazily, so no cloning happens when the method is called. What comes back is a view over the live source, and every enumeration of it calls DeepClone() on every element again. The XML doc (lines 104-105) promises "A new collection containing deep clones of the original items", and the method name suggests a snapshot. What callers actually get is:
the clone is not independent of the source. If the source list is changed or cleared after the call, the "clone" changes with it.
each enumeration yields a fresh set of clone instances, so changes made to items during one enumeration are silently gone the next time the sequence is read.
the extra enumerations are expensive. Count(), Any(), First(), or a second foreach clone the whole object graph again each time.
List<T>, arrays, Queue<T>, LinkedList<T>, and the other collections the README lists as supported all bind to this overload, so the problem affects the library's main use case.
Failure scenario
classItem:DeepCloneable<Item>{publicstringName="orig"; ...}varsrc=newList<Item>{new()};IEnumerable<Item>clone=src.DeepClone();foreach(varxinclone)x.Name="edited";clone.First().Name;// "orig" (expected "edited")ReferenceEquals(clone.First(),clone.First());// falsesrc.Clear();clone.Count();// 0 (expected 1: the clone should not track the source)
Reproduced by compiling the library sources into a net10.0 console app.
Suggested fix
Make the clone when the method is called, and null-check the source like the other overloads do:
publicstaticIEnumerable<T>DeepClone<T>(thisIEnumerable<T>source){Ensure.NotNull(source);returnsource.Select(DeepClone).ToArray();// or ToList()}
(Stack<T>.DeepClone at line 235 also has no Ensure.NotNull, unlike its sibling overloads.) Add a test that edits the items of a cloned sequence and then enumerates it again, and one that changes the source after cloning.
Notes: High because the library's main use case (cloning lists and arrays) returns a lazy view instead of a snapshot. Edits to the clone are lost, the clone tracks the source, and each enumeration clones the whole graph again. Materializing the result is a one-line fix. Returning a materialized result changes behaviour visibly, so note it in the changelog. Coordinate with #79 to avoid a merge conflict in DeepCloneContainerExtensions.cs.
Activity
matt-edmondson commentedon Sep 26, 2026
Triage
DeepCloneContainerExtensions.DeepClone<T>(IEnumerable<T>)(plus the missingEnsure.NotNullonStack<T>.DeepClone)Notes: High because the library's main use case (cloning lists and arrays) returns a lazy view instead of a snapshot. Edits to the clone are lost, the clone tracks the source, and each enumeration clones the whole graph again. Materializing the result is a one-line fix. Returning a materialized result changes behaviour visibly, so note it in the changelog. Coordinate with #79 to avoid a merge conflict in
DeepCloneContainerExtensions.cs.Generated by Claude Code