-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added DictionaryExtensions.Merge function
- Loading branch information
1 parent
51e50a0
commit 36c4187
Showing
6 changed files
with
199 additions
and
2 deletions.
There are no files selected for viewing
63 changes: 63 additions & 0 deletions
63
src/Extended.Collections.Tests/DictionaryExtensionsTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
using Extended.Collections.Exceptions; | ||
using FluentAssertions; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
namespace Extended.Collections.Tests | ||
{ | ||
public class DictionaryExtensionsTests : BaseTest | ||
{ | ||
|
||
public DictionaryExtensionsTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) | ||
{ } | ||
|
||
[Theory] | ||
[InlineData(MergeMethod.KeepLast)] | ||
[InlineData(MergeMethod.KeepFirst)] | ||
public void Merge_Single_KeepsExpectedValue(MergeMethod mergeMethod) | ||
{ | ||
Dictionary<string, MergeMethod> source = new() { ["Key"] = MergeMethod.KeepFirst }; | ||
Dictionary<string, MergeMethod> target = new() { ["Key"] = MergeMethod.KeepLast }; | ||
|
||
source.Merge(mergeMethod, target) | ||
.Should() | ||
.HaveCount(1) | ||
.And.Contain("Key", mergeMethod); | ||
} | ||
|
||
[Theory] | ||
[InlineData(MergeMethod.KeepLast)] | ||
[InlineData(MergeMethod.KeepFirst)] | ||
public void Merge_Multiple_KeepsExpectedValue(MergeMethod mergeMethod) | ||
{ | ||
Dictionary<string, string> source = Create("Key", "First"); | ||
Dictionary<string, string>[] targets = new[] { | ||
Create("Key", "Second"), | ||
Create("Key", "Third"), | ||
}; | ||
|
||
source.Merge(mergeMethod, targets) | ||
.Should() | ||
.HaveCount(1) | ||
.And.Contain("Key", mergeMethod == MergeMethod.KeepFirst ? "First" : "Third"); | ||
} | ||
|
||
[Fact] | ||
public void Merge_WithDuplicateKeysAndThrowMethod_ThrowsDuplicateKeyException() | ||
{ | ||
Dictionary<string, string> source = Create("key", "First"); | ||
|
||
Assert.Throws<DuplicateKeyException>( | ||
() => source.Merge(MergeMethod.Throw, source)); | ||
} | ||
|
||
|
||
private Dictionary<TKey, TValue> Create<TKey, TValue>(TKey key, TValue value) where TKey : notnull | ||
{ | ||
return new Dictionary<TKey, TValue> | ||
{ | ||
[key] = value | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
using Extended.Collections.Exceptions; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics.CodeAnalysis; | ||
|
||
namespace Extended.Collections | ||
{ | ||
/// <summary> | ||
/// Contains extension methods for working with <see cref="IDictionary{TKey, TValue}"/> | ||
/// </summary> | ||
public static class BaseDictionaryExtension | ||
{ | ||
[ExcludeFromCodeCoverage] | ||
public static IDictionary<TKey,TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> source, IEqualityComparer<TKey>? equalityComparer, IDictionary<TKey, TValue> target) | ||
=> Merge(source, MergeMethod.KeepLast, equalityComparer, new[] { target }); | ||
|
||
[ExcludeFromCodeCoverage] | ||
public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> source, IDictionary<TKey, TValue> target) | ||
=> Merge(source, MergeMethod.KeepLast, null, new[] { target }); | ||
|
||
[ExcludeFromCodeCoverage] | ||
public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> source, MergeMethod method, IDictionary<TKey, TValue> target) | ||
=> Merge(source, method, null, new IDictionary<TKey, TValue>[] { target }); | ||
|
||
|
||
[ExcludeFromCodeCoverage] | ||
public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> source, params IDictionary<TKey, TValue>[] targets) | ||
=> Merge(source, MergeMethod.KeepLast, null, targets); | ||
|
||
/// <inheritdoc cref="Merge{TKey, TValue}(IDictionary{TKey, TValue}, MergeMethod, IEqualityComparer{TKey}?, IDictionary{TKey, TValue}[])"/> | ||
[ExcludeFromCodeCoverage] | ||
public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> source, MergeMethod mergeMethod, params IDictionary<TKey, TValue>[] targets) | ||
=> Merge(source, mergeMethod, null, targets); | ||
|
||
/// <summary> | ||
/// Merges instances of <see cref="IDictionary{TKey, TValue}"/> into a unifed instance with the option of choosing how they are merged. | ||
/// </summary> | ||
/// <typeparam name="TKey">The key type</typeparam> | ||
/// <typeparam name="TValue">The value type to store</typeparam> | ||
/// <param name="source">The base value to start merging with.</param> | ||
/// <param name="equalityComparer">How keys will be compaired between two dictionaries</param> | ||
/// <param name="targets">The target instances to merge into the dictionary</param> | ||
/// <returns>The result of the merging of objects</returns> | ||
/// <exception cref="ArgumentNullException">The <paramref name="source"/> was null</exception> | ||
/// <exception cref="DuplicateKeyException">Two dictionaries contained the same key and the <paramref name="mergeMethod"/> was set to <see cref="MergeMethod.Throw"/></exception> | ||
[ExcludeFromCodeCoverage] | ||
public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> source, IEqualityComparer<TKey>? equalityComparer, params IDictionary<TKey, TValue>[] targets) | ||
=> Merge(source, MergeMethod.KeepLast, equalityComparer, targets); | ||
|
||
/// <summary> | ||
/// Merges instances of <see cref="IDictionary{TKey, TValue}"/> into a unifed instance with the option of choosing how they are merged. | ||
/// </summary> | ||
/// <typeparam name="TKey">The key type</typeparam> | ||
/// <typeparam name="TValue">The value type to store</typeparam> | ||
/// <param name="source">The base value to start merging with.</param> | ||
/// <param name="mergeMethod">The mergeMethod which is used to merge duplicate keys</param> | ||
/// <param name="equalityComparer">How keys will be compaired between two dictionaries</param> | ||
/// <param name="targets">The target instances to merge into the dictionary</param> | ||
/// <returns>The result of the merging of objects</returns> | ||
/// <exception cref="ArgumentNullException">The <paramref name="source"/> was null</exception> | ||
/// <exception cref="DuplicateKeyException">Two dictionaries contained the same key and the <paramref name="mergeMethod"/> was set to <see cref="MergeMethod.Throw"/></exception> | ||
public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> source, MergeMethod mergeMethod, IEqualityComparer<TKey>? equalityComparer, params IDictionary<TKey, TValue>[] targets) | ||
{ | ||
if(source == null) throw new ArgumentNullException(nameof(source)); | ||
|
||
equalityComparer ??= EqualityComparer<TKey>.Default; | ||
|
||
IDictionary<TKey, TValue> merged = new Dictionary<TKey, TValue>(source, equalityComparer); | ||
|
||
foreach (IDictionary<TKey, TValue> other in targets) | ||
{ | ||
foreach(KeyValuePair<TKey, TValue> pair in other) | ||
{ | ||
if (merged.ContainsKey(pair.Key)) | ||
{ | ||
switch(mergeMethod) | ||
{ | ||
case MergeMethod.KeepFirst: | ||
continue; | ||
case MergeMethod.KeepLast: | ||
merged[pair.Key] = pair.Value; | ||
break; | ||
case MergeMethod.Throw: | ||
throw new DuplicateKeyException(pair.Key); | ||
} | ||
} | ||
else | ||
{ | ||
merged.Add(pair.Key, pair.Value); | ||
} | ||
} | ||
} | ||
return merged; | ||
} | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
src/Extended.Collections/Exceptions/DuplicateKeyException.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
using System; | ||
|
||
namespace Extended.Collections.Exceptions | ||
{ | ||
public class DuplicateKeyException : Exception | ||
{ | ||
public object? Key { get; } | ||
|
||
public DuplicateKeyException(object? key) : base($"The key {key} already exists in the collection") | ||
{ | ||
Key = key; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
using Extended.Collections.Exceptions; | ||
|
||
namespace Extended.Collections | ||
{ | ||
/// <summary> | ||
/// Describes the different methods used for merging key value pairs | ||
/// </summary> | ||
public enum MergeMethod | ||
{ | ||
/// <summary> | ||
/// If two values share the same key throw a <see cref="DuplicateKeyException"/> will be thrown | ||
/// </summary> | ||
Throw, | ||
/// <summary> | ||
/// Keep the first value and discard any future changes | ||
/// </summary> | ||
KeepFirst, | ||
/// <summary> | ||
/// Discard the current value and use the new one. | ||
/// </summary> | ||
KeepLast, | ||
} | ||
} |