From 33d055c75b455eacf62add2ca41182b541b47981 Mon Sep 17 00:00:00 2001
From: talmroth <talmroth@gmail.com>
Date: Mon, 15 Mar 2021 12:26:01 -0700
Subject: [PATCH] Add TryGetValue (#142)

Co-authored-by: Tom Almroth <TAlmroth@loandepot.com>
---
 .../CachingServiceMemoryCacheProviderTests.cs | 25 ++++++++++++++++---
 LazyCache/CachingService.cs                   | 15 ++++++++---
 LazyCache/IAppCache.cs                        |  2 ++
 LazyCache/ICacheProvider.cs                   |  1 +
 LazyCache/Mocks/MockCacheProvider.cs          |  6 +++++
 LazyCache/Mocks/MockCachingService.cs         |  6 +++++
 LazyCache/Providers/MemoryCacheProvider.cs    | 10 ++++++--
 7 files changed, 55 insertions(+), 10 deletions(-)

diff --git a/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs b/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs
index 42aa3b9..f381820 100644
--- a/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs
+++ b/LazyCache.UnitTests/CachingServiceMemoryCacheProviderTests.cs
@@ -32,7 +32,7 @@ private static CachingService BuildCache()
 
         private class ComplexTestObject
         {
-            public readonly IList<object> SomeItems = new List<object> {1, 2, 3, "testing123"};
+            public readonly IList<object> SomeItems = new List<object> { 1, 2, 3, "testing123" };
             public string SomeMessage = "testing123";
         }
 
@@ -528,7 +528,7 @@ public async Task
                 Thread.Sleep(500);
 
             Assert.That(callbackValue, Is.AssignableTo<Task<int>>());
-            var callbackResultValue = await (Task<int>) callbackValue;
+            var callbackResultValue = await (Task<int>)callbackValue;
             Assert.AreEqual(123, callbackResultValue);
         }
 
@@ -873,7 +873,7 @@ MemoryCacheEntryOptions GetOptions()
                     .SetAbsoluteExpiration(refreshInterval, ExpirationMode.ImmediateEviction);
                 options.RegisterPostEvictionCallback((keyEvicted, value, reason, state) =>
                 {
-                    if (reason == EvictionReason.Expired  || reason == EvictionReason.TokenExpired)
+                    if (reason == EvictionReason.Expired || reason == EvictionReason.TokenExpired)
                         sut.GetOrAdd(key, _ => GetStuff(), GetOptions());
                 });
                 return options;
@@ -946,7 +946,7 @@ ComplexTestObject GetStuff()
                     {
                         var key = $"stuff-{hits % uniqueCacheItems}";
                         var cached = await sut.GetOrAddAsync(key, () => GetStuffAsync(), DateTimeOffset.UtcNow.AddSeconds(1));
-                        if(!cancel.IsCancellationRequested) Interlocked.Increment(ref hits);
+                        if (!cancel.IsCancellationRequested) Interlocked.Increment(ref hits);
                     }
                 });
             });
@@ -1108,5 +1108,22 @@ public void RemovedItemCannotBeRetrievedFromCache()
             sut.Remove(TestKey);
             Assert.Null(sut.Get<object>(TestKey));
         }
+
+        [Test]
+        public void TryGetReturnsCachedValueAndTrue()
+        {
+            string val = "Test Value";
+            string key = "testkey";
+            sut.Add(key, val);
+
+            var contains = sut.TryGetValue<string>(key, out var value);
+
+            Assert.IsTrue(contains);
+            Assert.AreEqual(value, val);
+
+            var contains2 = sut.TryGetValue<string>("invalidkey", out var value2);
+
+            Assert.IsFalse(contains2);
+        }
     }
 }
\ No newline at end of file
diff --git a/LazyCache/CachingService.cs b/LazyCache/CachingService.cs
index 30b6754..6becdd3 100644
--- a/LazyCache/CachingService.cs
+++ b/LazyCache/CachingService.cs
@@ -41,7 +41,7 @@ public CachingService(ICacheProvider cache) : this(() => cache)
         }
 
         public static Lazy<ICacheProvider> DefaultCacheProvider { get; set; }
-            = new Lazy<ICacheProvider>(() => 
+            = new Lazy<ICacheProvider>(() =>
                 new MemoryCacheProvider(
                     new MemoryCache(
                         new MemoryCacheOptions())
@@ -89,6 +89,13 @@ public virtual Task<T> GetAsync<T>(string key)
             return GetValueFromAsyncLazy<T>(item, out _);
         }
 
+        public virtual bool TryGetValue<T>(string key, out object value)
+        {
+            ValidateKey(key);
+
+            return CacheProvider.TryGetValue(key, out value);
+        }
+
         public virtual T GetOrAdd<T>(string key, Func<ICacheEntry, T> addItemFactory)
         {
             return GetOrAdd(key, addItemFactory, null);
@@ -112,7 +119,7 @@ object CacheFactory(ICacheEntry entry) =>
             // acquire lock per key
             uint hash = (uint)key.GetHashCode() % (uint)keyLocks.Length;
             while (Interlocked.CompareExchange(ref keyLocks[hash], 1, 0) == 1) { Thread.Yield(); }
-            
+
             try
             {
                 cacheItem = CacheProvider.GetOrCreate<object>(key, policy, CacheFactory);
@@ -179,7 +186,7 @@ public virtual Task<T> GetOrAddAsync<T>(string key, Func<ICacheEntry, Task<T>> a
 
         public virtual async Task<T> GetOrAddAsync<T>(string key, Func<ICacheEntry, Task<T>> addItemFactory,
             MemoryCacheEntryOptions policy)
-        { 
+        {
             ValidateKey(key);
 
             object cacheItem;
@@ -280,7 +287,7 @@ protected virtual T GetValueFromLazy<T>(object item, out bool valueHasChangedTyp
 
         protected virtual Task<T> GetValueFromAsyncLazy<T>(object item, out bool valueHasChangedType)
         {
-            valueHasChangedType = false; 
+            valueHasChangedType = false;
             switch (item)
             {
                 case AsyncLazy<T> asyncLazy:
diff --git a/LazyCache/IAppCache.cs b/LazyCache/IAppCache.cs
index b171411..3d9133d 100644
--- a/LazyCache/IAppCache.cs
+++ b/LazyCache/IAppCache.cs
@@ -15,6 +15,8 @@ public interface IAppCache
         void Add<T>(string key, T item, MemoryCacheEntryOptions policy);
         T Get<T>(string key);
         Task<T> GetAsync<T>(string key);
+        bool TryGetValue<T>(string key, out object value);
+
         T GetOrAdd<T>(string key, Func<ICacheEntry, T> addItemFactory);
         T GetOrAdd<T>(string key, Func<ICacheEntry, T> addItemFactory, MemoryCacheEntryOptions policy);
         Task<T> GetOrAddAsync<T>(string key, Func<ICacheEntry, Task<T>> addItemFactory);
diff --git a/LazyCache/ICacheProvider.cs b/LazyCache/ICacheProvider.cs
index 83885d6..7a3ca0b 100644
--- a/LazyCache/ICacheProvider.cs
+++ b/LazyCache/ICacheProvider.cs
@@ -12,5 +12,6 @@ public interface ICacheProvider : IDisposable
         object GetOrCreate<T>(string key, MemoryCacheEntryOptions policy, Func<ICacheEntry, T> func);
         void Remove(string key);
         Task<T> GetOrCreateAsync<T>(string key, Func<ICacheEntry, Task<T>> func);
+        bool TryGetValue(object key, out object value);
     }
 }
\ No newline at end of file
diff --git a/LazyCache/Mocks/MockCacheProvider.cs b/LazyCache/Mocks/MockCacheProvider.cs
index e8e2691..46bbeff 100644
--- a/LazyCache/Mocks/MockCacheProvider.cs
+++ b/LazyCache/Mocks/MockCacheProvider.cs
@@ -4,6 +4,7 @@
 
 namespace LazyCache.Mocks
 {
+
     public class MockCacheProvider : ICacheProvider
     {
         public void Set(string key, object item, MemoryCacheEntryOptions policy)
@@ -34,6 +35,11 @@ public Task<T> GetOrCreateAsync<T>(string key, Func<ICacheEntry, Task<T>> func)
             return func(null);
         }
 
+        public bool TryGetValue(object key, out object value)
+        {
+            throw new NotImplementedException();
+        }
+
         public void Dispose()
         {
         }
diff --git a/LazyCache/Mocks/MockCachingService.cs b/LazyCache/Mocks/MockCachingService.cs
index 71016c9..9c9be86 100644
--- a/LazyCache/Mocks/MockCachingService.cs
+++ b/LazyCache/Mocks/MockCachingService.cs
@@ -51,5 +51,11 @@ public Task<T> GetAsync<T>(string key)
         public void Add<T>(string key, T item, MemoryCacheEntryOptions policy)
         {
         }
+
+        public bool TryGetValue<T>(string key, out object value)
+        {
+            value = default(T);
+            return true;
+        }
     }
 }
\ No newline at end of file
diff --git a/LazyCache/Providers/MemoryCacheProvider.cs b/LazyCache/Providers/MemoryCacheProvider.cs
index 3884c69..72739db 100644
--- a/LazyCache/Providers/MemoryCacheProvider.cs
+++ b/LazyCache/Providers/MemoryCacheProvider.cs
@@ -32,7 +32,7 @@ public object GetOrCreate<T>(string key, Func<ICacheEntry, T> factory)
 
         public object GetOrCreate<T>(string key, MemoryCacheEntryOptions policy, Func<ICacheEntry, T> factory)
         {
-            if(policy == null)
+            if (policy == null)
                 return cache.GetOrCreate(key, factory);
 
             if (!cache.TryGetValue(key, out var result))
@@ -47,7 +47,7 @@ public object GetOrCreate<T>(string key, MemoryCacheEntryOptions policy, Func<IC
                     var expiryTokenSource = new CancellationTokenSource();
                     var expireToken = new CancellationChangeToken(expiryTokenSource.Token);
                     entry.AddExpirationToken(expireToken);
-                    entry.RegisterPostEvictionCallback((keyPost, value, reason, state) => 
+                    entry.RegisterPostEvictionCallback((keyPost, value, reason, state) =>
                         expiryTokenSource.Dispose());
 
                     result = factory(entry);
@@ -78,6 +78,12 @@ public Task<T> GetOrCreateAsync<T>(string key, Func<ICacheEntry, Task<T>> factor
             return cache.GetOrCreateAsync(key, factory);
         }
 
+        public bool TryGetValue(object key, out object value)
+        {
+            return cache.TryGetValue(key, out value);
+        }
+
+
         public void Dispose()
         {
             cache?.Dispose();