using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Collections.Concurrent; using System.Threading; namespace Jace.Util { /// /// An in-memory based cache to store objects. The implementation is thread safe and supports /// the multiple platforms supported by Jace (.NET, WinRT, WP7 and WP8). /// /// The type of the keys. /// The type of the values. public class MemoryCache { private readonly int maximumSize; private readonly int reductionSize; private long counter; // We cannot use DateTime.Now, because the precission is not high enough. private readonly ConcurrentDictionary dictionary; /// /// Create a new instance of the . /// /// The maximum allowed number of items in the cache. /// The number of items to be deleted per cleanup of the cache. public MemoryCache(int maximumSize, int reductionSize) { if (maximumSize < 1) throw new ArgumentOutOfRangeException("maximumSize", "The maximum allowed number of items in the cache must be at least one."); if (reductionSize < 1) throw new ArgumentOutOfRangeException("reductionSize", "The cache reduction size must be at least one."); this.maximumSize = maximumSize; this.reductionSize = reductionSize; this.dictionary = new ConcurrentDictionary(); } /// /// Get the value in the cache for the given key. /// /// The key to lookup in the cache. /// The value for the given key. public TValue this[TKey key] { get { CacheItem cacheItem = dictionary[key]; cacheItem.Accessed(); return cacheItem.Value; } } /// /// Gets the number of items in the cache. /// public int Count { get { return dictionary.Count; } } /// /// Returns true if an item with the given key is present in the cache. /// /// The key to lookup in the cache. /// True if an item is present in the cache for the given key. public bool ContainsKey(TKey key) { return dictionary.ContainsKey(key); } public bool TryGetValue (TKey key, out TValue result) { if (dictionary.TryGetValue(key, out var cachedItem)) { cachedItem.Accessed(); result = cachedItem.Value; return true; } else { result = default(TValue); return false; } } /// /// If for a given key an item is present in the cache, this method will return /// the value for the given key. If no item is present in the cache for the given /// key, the valueFactory is executed to produce the value. This value is stored in /// the cache and returned to the caller. /// /// The key to lookup in the cache. /// The factory to produce the value matching with the key. /// The value for the given key. public TValue GetOrAdd(TKey key, Func valueFactory) { if (valueFactory == null) throw new ArgumentNullException("valueFactory"); CacheItem cacheItem = dictionary.GetOrAdd(key, k => { EnsureCacheStorageAvailable(); TValue value = valueFactory(k); return new CacheItem(this, valueFactory(k)); }); return cacheItem.Value; } /// /// Ensure that the cache has room for an additional item. /// If there is not enough room anymore, force a removal of oldest /// accessed items in the cache. /// private void EnsureCacheStorageAvailable() { if (dictionary.Count >= maximumSize) // >= because we want to add an item after this method { IList keysToDelete = (from p in dictionary.ToArray() where p.Key != null && p.Value != null orderby p.Value.LastAccessed ascending select p.Key).Take(reductionSize).ToList(); foreach (TKey key in keysToDelete) { CacheItem cacheItem; dictionary.TryRemove(key, out cacheItem); } } } private class CacheItem { private MemoryCache cache; public CacheItem(MemoryCache cache, TValue value) { this.cache = cache; this.Value = value; Accessed(); } public TValue Value { get; private set; } public long LastAccessed { get; private set; } public void Accessed() { this.LastAccessed = Interlocked.Increment(ref cache.counter); } } } }