From 0e9811680b421f113f79e4ab68428ded675b7d38 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 26 Sep 2026 01:24:07 +0000 Subject: [PATCH] Fix IReadOnlyDictionary DeepClone throwing InvalidCastException The read-only dictionary overload passed the DeepClone method group to ToDictionary, which bound it to KeyValuePair and built a dictionary keyed by pairs, so the cast back to IReadOnlyDictionary failed on every call. Clone keys and values individually, as the IDictionary overload does, and add the missing null guard. Fixes ktsu-dev/DeepClone#76 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Q8KgDr8CfZLWPQ5o6nZdcV --- DeepClone.Test/SpecializedCollectionTests.cs | 56 ++++++++++++++++++++ DeepClone/DeepCloneContainerExtensions.cs | 11 +++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/DeepClone.Test/SpecializedCollectionTests.cs b/DeepClone.Test/SpecializedCollectionTests.cs index 8659464..a0db437 100644 --- a/DeepClone.Test/SpecializedCollectionTests.cs +++ b/DeepClone.Test/SpecializedCollectionTests.cs @@ -39,6 +39,62 @@ public void HashSet_DeepClone_ShouldCreateIndependentCopy() Assert.AreEqual("Item1", originalItem.Name); } + /// + /// Tests deep cloning an IReadOnlyDictionary. + /// + [TestMethod] + public void ReadOnlyDictionary_DeepClone_ShouldCreateIndependentCopy() + { + // Arrange + Dictionary original = new() + { + ["a"] = new() { Id = 1, Name = "Item1" }, + ["b"] = new() { Id = 2, Name = "Item2" } + }; + + // Act + IReadOnlyDictionary clone = ((IReadOnlyDictionary)original).DeepClone(); + + // Assert + Assert.IsNotNull(clone); + Assert.HasCount(original.Count, clone); + Assert.AreEqual(1, clone["a"].Id); + Assert.AreEqual("Item2", clone["b"].Name); + Assert.AreNotSame(original["a"], clone["a"]); + + // Verify independence + clone["a"].Name = "Modified"; + Assert.AreEqual("Item1", original["a"].Name); + } + + /// + /// Tests deep cloning an empty IReadOnlyDictionary. + /// + [TestMethod] + public void ReadOnlyDictionary_DeepClone_Empty_ShouldReturnEmptyDictionary() + { + // Arrange + Dictionary original = []; + + // Act + IReadOnlyDictionary clone = ((IReadOnlyDictionary)original).DeepClone(); + + // Assert + Assert.IsNotNull(clone); + Assert.IsEmpty(clone); + } + + /// + /// Tests that deep cloning a null IReadOnlyDictionary throws. + /// + [TestMethod] + public void ReadOnlyDictionary_DeepClone_Null_ShouldThrow() + { + IReadOnlyDictionary original = null!; + + Assert.ThrowsExactly(() => original.DeepClone()); + } + /// /// Tests deep cloning a SortedSet. /// diff --git a/DeepClone/DeepCloneContainerExtensions.cs b/DeepClone/DeepCloneContainerExtensions.cs index 6d2b6f8..b69f53f 100644 --- a/DeepClone/DeepCloneContainerExtensions.cs +++ b/DeepClone/DeepCloneContainerExtensions.cs @@ -160,9 +160,16 @@ public static IDictionary DeepClone(this IDictionary /// var clonedReadOnlyDict = originalReadOnlyDict.DeepClone(); /// /// + /// Thrown if source is null. public static IReadOnlyDictionary DeepClone(this IReadOnlyDictionary source) - where TKey : notnull => - (IReadOnlyDictionary)source.ToDictionary(DeepClone, DeepClone); + where TKey : notnull + { + Ensure.NotNull(source); + + return source.ToDictionary( + pair => DeepClone(pair.Key), + pair => DeepClone(pair.Value)); + } /// /// Deep clones a hash set.