From e086194db149748dc4cd22a51428dec21bde0ea4 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Wed, 6 Sep 2023 13:13:22 -0700 Subject: [PATCH 1/7] ext --- .../Atomic/ConcurrentDictionaryExtensions.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs diff --git a/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs b/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs new file mode 100644 index 00000000..e6382639 --- /dev/null +++ b/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Concurrent; + +namespace BitFaster.Caching.Atomic +{ + public static class ConcurrentDictionaryExtensions + { + public static V GetOrAdd(this ConcurrentDictionary> cache, K key, Func valueFactory) + { + var atomicFactory = cache.GetOrAdd(key, _ => new AtomicFactory()); + return atomicFactory.GetValue(key, valueFactory); + } + } +} From 49bf25e30daab1ff1824bc314a4d6d75e95cbe09 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Mon, 11 Sep 2023 22:37:30 -0700 Subject: [PATCH 2/7] arg --- .../Atomic/AtomicFactoryCache.cs | 1 - .../Atomic/ConcurrentDictionaryExtensions.cs | 24 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/BitFaster.Caching/Atomic/AtomicFactoryCache.cs b/BitFaster.Caching/Atomic/AtomicFactoryCache.cs index 6af07f8c..0433b682 100644 --- a/BitFaster.Caching/Atomic/AtomicFactoryCache.cs +++ b/BitFaster.Caching/Atomic/AtomicFactoryCache.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Linq.Expressions; namespace BitFaster.Caching.Atomic { diff --git a/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs b/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs index e6382639..769f4e98 100644 --- a/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs +++ b/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs @@ -3,6 +3,9 @@ namespace BitFaster.Caching.Atomic { + /// + /// Convenience methods for using AtomicFactory with ConcurrentDictionary. + /// public static class ConcurrentDictionaryExtensions { public static V GetOrAdd(this ConcurrentDictionary> cache, K key, Func valueFactory) @@ -10,5 +13,26 @@ public static V GetOrAdd(this ConcurrentDictionary> var atomicFactory = cache.GetOrAdd(key, _ => new AtomicFactory()); return atomicFactory.GetValue(key, valueFactory); } + + public static V GetOrAdd(this ConcurrentDictionary> cache, K key, Func valueFactory, TArg factoryArgument) + { + var atomicFactory = cache.GetOrAdd(key, _ => new AtomicFactory()); + return atomicFactory.GetValue(key, valueFactory, factoryArgument); + } + + public static bool TryGetValue(this ConcurrentDictionary> cache, K key, out V value) + { + AtomicFactory output; + var ret = cache.TryGetValue(key, out output); + + if (ret && output.IsValueCreated) + { + value = output.ValueIfCreated; + return true; + } + + value = default; + return false; + } } } From 8ed8be0b66b4508d70b7df0677facf3845343793 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Tue, 12 Sep 2023 13:27:08 -0700 Subject: [PATCH 3/7] try rem --- .../Atomic/ConcurrentDictionaryExtensions.cs | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs b/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs index 769f4e98..c57756f3 100644 --- a/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs +++ b/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; - +using System.Collections.Generic; + namespace BitFaster.Caching.Atomic { /// @@ -31,6 +32,29 @@ public static bool TryGetValue(this ConcurrentDictionary(this ConcurrentDictionary> cache, KeyValuePair item) + { + var kvp = new KeyValuePair>(item.Key, new AtomicFactory(item.Value)); +#if NET6_0_OR_GREATER + return cache.TryRemove(kvp); +#else + // https://devblogs.microsoft.com/pfxteam/little-known-gems-atomic-conditional-removals-from-concurrentdictionary/ + return ((ICollection>>)cache).Remove(kvp); +#endif + } + + public static bool TryRemove(this ConcurrentDictionary> cache, K key, out V value) + { + if (cache.TryRemove(key, out var atomic)) + { + value = atomic.ValueIfCreated; + return true; + } + value = default; return false; } From a8c58bc22095eccf56022f64f514550c2c263d45 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Tue, 12 Sep 2023 14:31:54 -0700 Subject: [PATCH 4/7] docs+test --- .../ConcurrentDictionaryExtensionTests.cs | 22 +++++++ .../Atomic/ConcurrentDictionaryExtensions.cs | 57 +++++++++++++++---- 2 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs diff --git a/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs b/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs new file mode 100644 index 00000000..0307acf0 --- /dev/null +++ b/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs @@ -0,0 +1,22 @@ + +using System.Collections.Concurrent; +using BitFaster.Caching.Atomic; +using FluentAssertions; +using Xunit; + +namespace BitFaster.Caching.UnitTests.Atomic +{ + public class ConcurrentDictionaryExtensionTests + { + private ConcurrentDictionary> dictionary = new ConcurrentDictionary>(); + + [Fact] + public void GetOrAdd() + { + dictionary.GetOrAdd(1, k => k); + + dictionary.TryGetValue(1, out int value).Should().BeTrue(); + value.Should().Be(1); + } + } +} diff --git a/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs b/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs index c57756f3..333b8339 100644 --- a/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs +++ b/BitFaster.Caching/Atomic/ConcurrentDictionaryExtensions.cs @@ -9,22 +9,44 @@ namespace BitFaster.Caching.Atomic /// public static class ConcurrentDictionaryExtensions { - public static V GetOrAdd(this ConcurrentDictionary> cache, K key, Func valueFactory) + /// + /// Adds a key/value pair to the ConcurrentDictionary if the key does not already exist. Returns the new value, or the existing value if the key already exists. + /// + /// The ConcurrentDictionary to use. + /// The key of the element to add. + /// The function used to generate a value for the key. + /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new value if the key was not in the dictionary. + public static V GetOrAdd(this ConcurrentDictionary> dictionary, K key, Func valueFactory) { - var atomicFactory = cache.GetOrAdd(key, _ => new AtomicFactory()); + var atomicFactory = dictionary.GetOrAdd(key, _ => new AtomicFactory()); return atomicFactory.GetValue(key, valueFactory); } - public static V GetOrAdd(this ConcurrentDictionary> cache, K key, Func valueFactory, TArg factoryArgument) + /// + /// Adds a key/value pair to the ConcurrentDictionary by using the specified function and an argument if the key does not already exist, or returns the existing value if the key exists. + /// + /// The ConcurrentDictionary to use. + /// The key of the element to add. + /// The function used to generate a value for the key. + /// An argument value to pass into valueFactory. + /// The value for the key. This will be either the existing value for the key if the key is already in the dictionary, or the new value if the key was not in the dictionary. + public static V GetOrAdd(this ConcurrentDictionary> dictionary, K key, Func valueFactory, TArg factoryArgument) { - var atomicFactory = cache.GetOrAdd(key, _ => new AtomicFactory()); + var atomicFactory = dictionary.GetOrAdd(key, _ => new AtomicFactory()); return atomicFactory.GetValue(key, valueFactory, factoryArgument); } - public static bool TryGetValue(this ConcurrentDictionary> cache, K key, out V value) + /// + /// Attempts to get the value associated with the specified key from the ConcurrentDictionary. + /// + /// The ConcurrentDictionary to use. + /// The key of the value to get. + /// When this method returns, contains the object from the ConcurrentDictionary that has the specified key, or the default value of the type if the operation failed. + /// true if the key was found in the ConcurrentDictionary; otherwise, false. + public static bool TryGetValue(this ConcurrentDictionary> dictionary, K key, out V value) { AtomicFactory output; - var ret = cache.TryGetValue(key, out output); + var ret = dictionary.TryGetValue(key, out output); if (ret && output.IsValueCreated) { @@ -36,20 +58,33 @@ public static bool TryGetValue(this ConcurrentDictionary(this ConcurrentDictionary> cache, KeyValuePair item) + /// + /// Removes a key and value from the dictionary. + /// + /// The ConcurrentDictionary to use. + /// The KeyValuePair representing the key and value to remove. + /// true if the object was removed successfully; otherwise, false. + public static bool TryRemove(this ConcurrentDictionary> dictionary, KeyValuePair item) { var kvp = new KeyValuePair>(item.Key, new AtomicFactory(item.Value)); #if NET6_0_OR_GREATER - return cache.TryRemove(kvp); + return dictionary.TryRemove(kvp); #else // https://devblogs.microsoft.com/pfxteam/little-known-gems-atomic-conditional-removals-from-concurrentdictionary/ - return ((ICollection>>)cache).Remove(kvp); + return ((ICollection>>)dictionary).Remove(kvp); #endif } - public static bool TryRemove(this ConcurrentDictionary> cache, K key, out V value) + /// + /// Attempts to remove and return the value that has the specified key from the ConcurrentDictionary. + /// + /// The ConcurrentDictionary to use. + /// The key of the element to remove and return. + /// When this method returns, contains the object removed from the ConcurrentDictionary, or the default value of the TValue type if key does not exist. + /// true if the object was removed successfully; otherwise, false. + public static bool TryRemove(this ConcurrentDictionary> dictionary, K key, out V value) { - if (cache.TryRemove(key, out var atomic)) + if (dictionary.TryRemove(key, out var atomic)) { value = atomic.ValueIfCreated; return true; From faa984f296aa448f17101f6a7c08769874e24aed Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Wed, 13 Sep 2023 22:44:20 -0700 Subject: [PATCH 5/7] tests --- .../ConcurrentDictionaryExtensionTests.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs b/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs index 0307acf0..53e98574 100644 --- a/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs +++ b/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs @@ -1,5 +1,6 @@  using System.Collections.Concurrent; +using System.Collections.Generic; using BitFaster.Caching.Atomic; using FluentAssertions; using Xunit; @@ -11,12 +12,39 @@ public class ConcurrentDictionaryExtensionTests private ConcurrentDictionary> dictionary = new ConcurrentDictionary>(); [Fact] - public void GetOrAdd() + public void WhenItemIsAddedItCanBeRetrieved() { dictionary.GetOrAdd(1, k => k); dictionary.TryGetValue(1, out int value).Should().BeTrue(); value.Should().Be(1); + } + + [Fact] + public void WhenItemIsAddedWithArgItCanBeRetrieved() + { + dictionary.GetOrAdd(1, (k,a) => k + a, 2); + + dictionary.TryGetValue(1, out int value).Should().BeTrue(); + value.Should().Be(3); + } + + [Fact] + public void WhenItemIsAddedItCanBeRemovedByKey() + { + dictionary.GetOrAdd(1, k => k); + + dictionary.TryRemove(1, out int value).Should().BeTrue(); + value.Should().Be(1); + } + + [Fact] + public void WhenItemIsAddedItCanBeRemovedByKvp() + { + dictionary.GetOrAdd(1, k => k); + + dictionary.TryRemove(new KeyValuePair(1, 1)).Should().BeTrue(); + dictionary.TryGetValue(1, out _).Should().BeFalse(); } } } From 7e5f499eeab1632f3f4d1a87ee44e3be5f1bd18b Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Wed, 13 Sep 2023 22:54:56 -0700 Subject: [PATCH 6/7] tests --- .../Atomic/ConcurrentDictionaryExtensionTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs b/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs index 53e98574..a42b4e88 100644 --- a/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs +++ b/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs @@ -29,6 +29,12 @@ public void WhenItemIsAddedWithArgItCanBeRetrieved() value.Should().Be(3); } + [Fact] + public void WhenKeyDoesNotExistTryGetReturnsFalse() + { + dictionary.TryGetValue(1, out _).Should().BeFalse(); + } + [Fact] public void WhenItemIsAddedItCanBeRemovedByKey() { @@ -46,5 +52,11 @@ public void WhenItemIsAddedItCanBeRemovedByKvp() dictionary.TryRemove(new KeyValuePair(1, 1)).Should().BeTrue(); dictionary.TryGetValue(1, out _).Should().BeFalse(); } + + [Fact] + public void WhenKeyDoesNotExistTryRemoveReturnsFalse() + { + dictionary.TryRemove(1, out int _).Should().BeFalse(); + } } } From 9add5600a6f8f050c52dd14048e478a98724e347 Mon Sep 17 00:00:00 2001 From: Alex Peck Date: Wed, 13 Sep 2023 23:14:56 -0700 Subject: [PATCH 7/7] tryget --- .../Atomic/ConcurrentDictionaryExtensionTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs b/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs index a42b4e88..f5076a20 100644 --- a/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs +++ b/BitFaster.Caching.UnitTests/Atomic/ConcurrentDictionaryExtensionTests.cs @@ -32,7 +32,7 @@ public void WhenItemIsAddedWithArgItCanBeRetrieved() [Fact] public void WhenKeyDoesNotExistTryGetReturnsFalse() { - dictionary.TryGetValue(1, out _).Should().BeFalse(); + dictionary.TryGetValue(1, out int _).Should().BeFalse(); } [Fact]