Skip to content

Commit

Permalink
Add ShallowClone to enumerables and dictionaries
Browse files Browse the repository at this point in the history
  • Loading branch information
matt-edmondson committed Aug 8, 2024
1 parent 5f2f406 commit 0ebfae5
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 1 deletion.
109 changes: 109 additions & 0 deletions Extensions.Test/DictionaryExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, int>
{
{ "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<string, int> originalDict = null!;

// Act & Assert
Assert.ThrowsException<ArgumentNullException>(originalDict.ShallowClone);
}

[TestMethod]
public void ShallowCloneDictionary_WithLockObj_ShouldCloneDictionaryCorrectly()
{
// Arrange
var originalDict = new Dictionary<string, int>
{
{ "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<string, int> originalDict = null!;
object lockObj = new();

// Act & Assert
Assert.ThrowsException<ArgumentNullException>(() => originalDict.ShallowClone(lockObj));
}

[TestMethod]
public void ShallowCloneDictionary_WithLockObj_ShouldThrowArgumentNullException_WhenLockObjIsNull()
{
// Arrange
var originalDict = new Dictionary<string, int>
{
{ "One", 1 },
{ "Two", 2 },
{ "Three", 3 }
};
object lockObj = null!;

// Act & Assert
Assert.ThrowsException<ArgumentNullException>(() => originalDict.ShallowClone(lockObj));
}

[TestMethod]
public void ShallowCloneDictionary_WithLockObj_ShouldLockAndCloneCorrectly()
{
// Arrange
var originalDict = new Dictionary<string, int>
{
{ "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.");
}
}
87 changes: 87 additions & 0 deletions Extensions.Test/EnumerableExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<int> { 1, 2, 3, 4, 5 };

// Act
var clonedItems = originalItems.ShallowClone<int, List<int>>();

// 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<int> originalItems = null!;

// Act & Assert
Assert.ThrowsException<ArgumentNullException>(originalItems.ShallowClone<int, List<int>>);
}

[TestMethod]
public void ShallowClone_WithLockObj_ShouldCloneCollectionCorrectly()
{
// Arrange
var originalItems = new List<int> { 1, 2, 3, 4, 5 };
object lockObj = new();

// Act
var clonedItems = originalItems.ShallowClone<int, List<int>>(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<int> originalItems = null!;
object lockObj = new();

// Act & Assert
Assert.ThrowsException<ArgumentNullException>(() => originalItems.ShallowClone<int, List<int>>(lockObj));
}

[TestMethod]
public void ShallowClone_WithLockObj_ShouldThrowArgumentNullException_WhenLockObjIsNull()
{
// Arrange
var originalItems = new List<int> { 1, 2, 3, 4, 5 };
object lockObj = null!;

// Act & Assert
Assert.ThrowsException<ArgumentNullException>(() => originalItems.ShallowClone<int, List<int>>(lockObj));
}

[TestMethod]
public void ShallowClone_WithLockObj_ShouldLockAndCloneCorrectly()
{
// Arrange
var originalItems = new List<int> { 1, 2, 3, 4, 5 };
object lockObj = new();
bool wasLocked = false;

// Act
lock (lockObj)
{
wasLocked = true;
var clonedItems = originalItems.ShallowClone<int, List<int>>(lockObj);
wasLocked = false;
}

// Assert
Assert.IsFalse(wasLocked, "The lock object should have been released after the method executed.");
}
}
39 changes: 39 additions & 0 deletions Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,43 @@ public static IDictionary<TKey, TValue> DeepClone<TKey, TValue>(this IDictionary
return DeepClone(items);
}
}

/// <summary>
/// Creates a shallow clone of a dictionary, returning a new dictionary with the same keys and values.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary. Keys must be non-nullable.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
/// <param name="items">The dictionary to clone. This dictionary cannot be null.</param>
/// <returns>A new dictionary with the same keys and values as the input dictionary.</returns>
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="items"/> dictionary is null.</exception>
public static IDictionary<TKey, TValue> ShallowClone<TKey, TValue>(this IDictionary<TKey, TValue> items)
where TKey : notnull
{
ArgumentNullException.ThrowIfNull(items);
return items.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TKey">The type of the keys in the dictionary. Keys must be non-nullable.</typeparam>
/// <typeparam name="TValue">The type of the values in the dictionary.</typeparam>
/// <param name="items">The dictionary to clone. This dictionary cannot be null.</param>
/// <param name="lockObj">The object to lock on to ensure thread-safety during the cloning operation. This cannot be null.</param>
/// <returns>A new dictionary with the same keys and values as the input dictionary.</returns>
/// <exception cref="ArgumentNullException">
/// Thrown if the <paramref name="items"/> dictionary or the <paramref name="lockObj"/> is null.
/// </exception>
public static IDictionary<TKey, TValue> ShallowClone<TKey, TValue>(this IDictionary<TKey, TValue> items, object lockObj)
where TKey : notnull
{
ArgumentNullException.ThrowIfNull(items);
ArgumentNullException.ThrowIfNull(lockObj);
lock (lockObj)
{
return ShallowClone(items);
}
}

}
40 changes: 40 additions & 0 deletions Extensions/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,44 @@ public static TDest DeepClone<TItem, TDest>(this IEnumerable<TItem> items, objec
return DeepClone<TItem, TDest>(items);
}
}

/// <summary>
/// Creates a shallow clone of a collection of items, returning a new collection of the specified type.
/// </summary>
/// <typeparam name="TItem">The type of the items in the collection.</typeparam>
/// <typeparam name="TDest">The type of the destination collection, which must implement <see cref="ICollection{TItem}"/> and have a parameterless constructor.</typeparam>
/// <param name="items">The collection of items to clone. This collection cannot be null.</param>
/// <returns>A new collection of type <typeparamref name="TDest"/> containing the same items as the input collection.</returns>
/// <exception cref="ArgumentNullException">Thrown if the <paramref name="items"/> collection is null.</exception>
public static TDest ShallowClone<TItem, TDest>(this IEnumerable<TItem> items)

Check warning on line 127 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / build-dotnet-library / ktsu-io/Extensions build-dotnet-library

Refactor this method to use all type parameters in the parameter list to enable type inference. (https://rules.sonarsource.com/csharp/RSPEC-4018)

Check warning on line 127 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / build-dotnet-library / ktsu-io/Extensions build-dotnet-library

Refactor this method to use all type parameters in the parameter list to enable type inference. (https://rules.sonarsource.com/csharp/RSPEC-4018)
where TDest : ICollection<TItem>, new()
{
ArgumentNullException.ThrowIfNull(items);
var destination = new TDest();
destination.AddMany(items);
return destination;
}

/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TItem">The type of the items in the collection.</typeparam>
/// <typeparam name="TDest">The type of the destination collection, which must implement <see cref="ICollection{TItem}"/> and have a parameterless constructor.</typeparam>
/// <param name="items">The collection of items to clone. This collection cannot be null.</param>
/// <param name="lockObj">The object to lock on to ensure thread-safety during the cloning operation. This cannot be null.</param>
/// <returns>A new collection of type <typeparamref name="TDest"/> containing the same items as the input collection.</returns>
/// <exception cref="ArgumentNullException">
/// Thrown if the <paramref name="items"/> collection or the <paramref name="lockObj"/> is null.
/// </exception>
public static TDest ShallowClone<TItem, TDest>(this IEnumerable<TItem> items, object lockObj)

Check warning on line 148 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / build-dotnet-library / ktsu-io/Extensions build-dotnet-library

Refactor this method to use all type parameters in the parameter list to enable type inference. (https://rules.sonarsource.com/csharp/RSPEC-4018)

Check warning on line 148 in Extensions/EnumerableExtensions.cs

View workflow job for this annotation

GitHub Actions / build-dotnet-library / ktsu-io/Extensions build-dotnet-library

Refactor this method to use all type parameters in the parameter list to enable type inference. (https://rules.sonarsource.com/csharp/RSPEC-4018)
where TDest : ICollection<TItem>, new()
{
ArgumentNullException.ThrowIfNull(items);
ArgumentNullException.ThrowIfNull(lockObj);
lock (lockObj)
{
return ShallowClone<TItem, TDest>(items);
}
}
}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.4
1.0.5

0 comments on commit 0ebfae5

Please sign in to comment.