From 0ebfae54a259894298086de16c487d11ea506fe0 Mon Sep 17 00:00:00 2001 From: matt-edmondson Date: Thu, 8 Aug 2024 14:09:47 +1000 Subject: [PATCH] Add ShallowClone to enumerables and dictionaries --- Extensions.Test/DictionaryExtensionsTests.cs | 109 +++++++++++++++++++ Extensions.Test/EnumerableExtensionsTests.cs | 87 +++++++++++++++ Extensions/DictionaryExtensions.cs | 39 +++++++ Extensions/EnumerableExtensions.cs | 40 +++++++ README.md | 3 + VERSION | 2 +- 6 files changed, 279 insertions(+), 1 deletion(-) diff --git a/Extensions.Test/DictionaryExtensionsTests.cs b/Extensions.Test/DictionaryExtensionsTests.cs index 0ad454d..b90f1a2 100644 --- a/Extensions.Test/DictionaryExtensionsTests.cs +++ b/Extensions.Test/DictionaryExtensionsTests.cs @@ -208,4 +208,113 @@ public void DeepCloneDictionary_WithLockObj_ShouldLockAndCloneCorrectly() // Assert Assert.IsFalse(wasLocked, "The lock object should have been released after the method executed."); } + + [TestMethod] + public void ShallowCloneDictionary_ShouldCloneDictionaryCorrectly() + { + // Arrange + var originalDict = new Dictionary + { + { "One", 1 }, + { "Two", 2 }, + { "Three", 3 } + }; + + // Act + var clonedDict = originalDict.ShallowClone(); + + // Assert + Assert.AreEqual(originalDict.Count, clonedDict.Count); + foreach (var kvp in originalDict) + { + Assert.IsTrue(clonedDict.ContainsKey(kvp.Key)); + Assert.AreEqual(kvp.Value, clonedDict[kvp.Key]); + } + } + + [TestMethod] + public void ShallowCloneDictionary_ShouldThrowArgumentNullException_WhenItemsIsNull() + { + // Arrange + Dictionary originalDict = null!; + + // Act & Assert + Assert.ThrowsException(originalDict.ShallowClone); + } + + [TestMethod] + public void ShallowCloneDictionary_WithLockObj_ShouldCloneDictionaryCorrectly() + { + // Arrange + var originalDict = new Dictionary + { + { "One", 1 }, + { "Two", 2 }, + { "Three", 3 } + }; + object lockObj = new(); + + // Act + var clonedDict = originalDict.ShallowClone(lockObj); + + // Assert + Assert.AreEqual(originalDict.Count, clonedDict.Count); + foreach (var kvp in originalDict) + { + Assert.IsTrue(clonedDict.ContainsKey(kvp.Key)); + Assert.AreEqual(kvp.Value, clonedDict[kvp.Key]); + } + } + + [TestMethod] + public void ShallowCloneDictionary_WithLockObj_ShouldThrowArgumentNullException_WhenItemsIsNull() + { + // Arrange + Dictionary originalDict = null!; + object lockObj = new(); + + // Act & Assert + Assert.ThrowsException(() => originalDict.ShallowClone(lockObj)); + } + + [TestMethod] + public void ShallowCloneDictionary_WithLockObj_ShouldThrowArgumentNullException_WhenLockObjIsNull() + { + // Arrange + var originalDict = new Dictionary + { + { "One", 1 }, + { "Two", 2 }, + { "Three", 3 } + }; + object lockObj = null!; + + // Act & Assert + Assert.ThrowsException(() => originalDict.ShallowClone(lockObj)); + } + + [TestMethod] + public void ShallowCloneDictionary_WithLockObj_ShouldLockAndCloneCorrectly() + { + // Arrange + var originalDict = new Dictionary + { + { "One", 1 }, + { "Two", 2 }, + { "Three", 3 } + }; + object lockObj = new(); + bool wasLocked = false; + + // Act + lock (lockObj) + { + wasLocked = true; + var clonedDict = originalDict.ShallowClone(lockObj); + wasLocked = false; + } + + // Assert + Assert.IsFalse(wasLocked, "The lock object should have been released after the method executed."); + } } diff --git a/Extensions.Test/EnumerableExtensionsTests.cs b/Extensions.Test/EnumerableExtensionsTests.cs index a0c6d0a..b78c999 100644 --- a/Extensions.Test/EnumerableExtensionsTests.cs +++ b/Extensions.Test/EnumerableExtensionsTests.cs @@ -182,4 +182,91 @@ public void DeepClone_WithLockObj_ShouldLockAndCloneCorrectly() // Assert Assert.IsFalse(wasLocked, "The lock object should have been released after the method executed."); } + + [TestMethod] + public void ShallowClone_ShouldCloneCollectionCorrectly() + { + // Arrange + var originalItems = new List { 1, 2, 3, 4, 5 }; + + // Act + var clonedItems = originalItems.ShallowClone>(); + + // Assert + Assert.AreEqual(originalItems.Count, clonedItems.Count); + for (int i = 0; i < originalItems.Count; i++) + { + Assert.AreEqual(originalItems[i], clonedItems[i]); + } + } + + [TestMethod] + public void ShallowClone_ShouldThrowArgumentNullException_WhenItemsIsNull() + { + // Arrange + List originalItems = null!; + + // Act & Assert + Assert.ThrowsException(originalItems.ShallowClone>); + } + + [TestMethod] + public void ShallowClone_WithLockObj_ShouldCloneCollectionCorrectly() + { + // Arrange + var originalItems = new List { 1, 2, 3, 4, 5 }; + object lockObj = new(); + + // Act + var clonedItems = originalItems.ShallowClone>(lockObj); + + // Assert + Assert.AreEqual(originalItems.Count, clonedItems.Count); + for (int i = 0; i < originalItems.Count; i++) + { + Assert.AreEqual(originalItems[i], clonedItems[i]); + } + } + + [TestMethod] + public void ShallowClone_WithLockObj_ShouldThrowArgumentNullException_WhenItemsIsNull() + { + // Arrange + List originalItems = null!; + object lockObj = new(); + + // Act & Assert + Assert.ThrowsException(() => originalItems.ShallowClone>(lockObj)); + } + + [TestMethod] + public void ShallowClone_WithLockObj_ShouldThrowArgumentNullException_WhenLockObjIsNull() + { + // Arrange + var originalItems = new List { 1, 2, 3, 4, 5 }; + object lockObj = null!; + + // Act & Assert + Assert.ThrowsException(() => originalItems.ShallowClone>(lockObj)); + } + + [TestMethod] + public void ShallowClone_WithLockObj_ShouldLockAndCloneCorrectly() + { + // Arrange + var originalItems = new List { 1, 2, 3, 4, 5 }; + object lockObj = new(); + bool wasLocked = false; + + // Act + lock (lockObj) + { + wasLocked = true; + var clonedItems = originalItems.ShallowClone>(lockObj); + wasLocked = false; + } + + // Assert + Assert.IsFalse(wasLocked, "The lock object should have been released after the method executed."); + } } diff --git a/Extensions/DictionaryExtensions.cs b/Extensions/DictionaryExtensions.cs index 98aa7db..3087fac 100644 --- a/Extensions/DictionaryExtensions.cs +++ b/Extensions/DictionaryExtensions.cs @@ -108,4 +108,43 @@ public static IDictionary DeepClone(this IDictionary return DeepClone(items); } } + + /// + /// Creates a shallow clone of a dictionary, returning a new dictionary with the same keys and values. + /// + /// The type of the keys in the dictionary. Keys must be non-nullable. + /// The type of the values in the dictionary. + /// The dictionary to clone. This dictionary cannot be null. + /// A new dictionary with the same keys and values as the input dictionary. + /// Thrown if the dictionary is null. + public static IDictionary ShallowClone(this IDictionary items) + where TKey : notnull + { + ArgumentNullException.ThrowIfNull(items); + return items.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + /// + /// Creates a shallow clone of a dictionary, returning a new dictionary with the same keys and values, + /// with thread-safety ensured by locking on the provided object. + /// + /// The type of the keys in the dictionary. Keys must be non-nullable. + /// The type of the values in the dictionary. + /// The dictionary to clone. This dictionary cannot be null. + /// The object to lock on to ensure thread-safety during the cloning operation. This cannot be null. + /// A new dictionary with the same keys and values as the input dictionary. + /// + /// Thrown if the dictionary or the is null. + /// + public static IDictionary ShallowClone(this IDictionary items, object lockObj) + where TKey : notnull + { + ArgumentNullException.ThrowIfNull(items); + ArgumentNullException.ThrowIfNull(lockObj); + lock (lockObj) + { + return ShallowClone(items); + } + } + } diff --git a/Extensions/EnumerableExtensions.cs b/Extensions/EnumerableExtensions.cs index af90e75..9a79516 100644 --- a/Extensions/EnumerableExtensions.cs +++ b/Extensions/EnumerableExtensions.cs @@ -115,4 +115,44 @@ public static TDest DeepClone(this IEnumerable items, objec return DeepClone(items); } } + + /// + /// Creates a shallow clone of a collection of items, returning a new collection of the specified type. + /// + /// The type of the items in the collection. + /// The type of the destination collection, which must implement and have a parameterless constructor. + /// The collection of items to clone. This collection cannot be null. + /// A new collection of type containing the same items as the input collection. + /// Thrown if the collection is null. + public static TDest ShallowClone(this IEnumerable items) + where TDest : ICollection, new() + { + ArgumentNullException.ThrowIfNull(items); + var destination = new TDest(); + destination.AddMany(items); + return destination; + } + + /// + /// Creates a shallow clone of a collection of items, returning a new collection of the specified type, + /// with thread-safety ensured by locking on the provided object. + /// + /// The type of the items in the collection. + /// The type of the destination collection, which must implement and have a parameterless constructor. + /// The collection of items to clone. This collection cannot be null. + /// The object to lock on to ensure thread-safety during the cloning operation. This cannot be null. + /// A new collection of type containing the same items as the input collection. + /// + /// Thrown if the collection or the is null. + /// + public static TDest ShallowClone(this IEnumerable items, object lockObj) + where TDest : ICollection, new() + { + ArgumentNullException.ThrowIfNull(items); + ArgumentNullException.ThrowIfNull(lockObj); + lock (lockObj) + { + return ShallowClone(items); + } + } } diff --git a/README.md b/README.md index 4d136c6..39155d5 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,12 @@ Extension Methods for common operations to help with expressiveness and maintain - ToCollection (with optional lock) - ForEach - DeepClone (to a collection, with optional lock) +- ShallowClone (to a collection, with optional lock) ## Dictionary Extensions - GetOrCreate +- DeepClone (with optional lock) +- ShallowClone (with optional lock) ## Collection Extensions - AddMany diff --git a/VERSION b/VERSION index ee90284..90a27f9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.4 +1.0.5