/
MemoryCache.cs
165 lines (143 loc) · 5.77 KB
/
MemoryCache.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Threading;
namespace Jace.Util
{
/// <summary>
/// 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).
/// </summary>
/// <typeparam name="TKey">The type of the keys.</typeparam>
/// <typeparam name="TValue">The type of the values.</typeparam>
public class MemoryCache<TKey, TValue>
{
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<TKey, CacheItem> dictionary;
/// <summary>
/// Create a new instance of the <see cref="MemoryCache"/>.
/// </summary>
/// <param name="maximumSize">The maximum allowed number of items in the cache.</param>
/// <param name="reductionSize">The number of items to be deleted per cleanup of the cache.</param>
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<TKey, CacheItem>();
}
/// <summary>
/// Get the value in the cache for the given key.
/// </summary>
/// <param name="key">The key to lookup in the cache.</param>
/// <returns>The value for the given key.</returns>
public TValue this[TKey key]
{
get
{
CacheItem cacheItem = dictionary[key];
cacheItem.Accessed();
return cacheItem.Value;
}
}
/// <summary>
/// Gets the number of items in the cache.
/// </summary>
public int Count
{
get
{
return dictionary.Count;
}
}
/// <summary>
/// Returns true if an item with the given key is present in the cache.
/// </summary>
/// <param name="key">The key to lookup in the cache.</param>
/// <returns>True if an item is present in the cache for the given key.</returns>
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;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="key">The key to lookup in the cache.</param>
/// <param name="valueFactory">The factory to produce the value matching with the key.</param>
/// <returns>The value for the given key.</returns>
public TValue GetOrAdd(TKey key, Func<TKey, TValue> 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;
}
/// <summary>
/// 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.
/// </summary>
private void EnsureCacheStorageAvailable()
{
if (dictionary.Count >= maximumSize) // >= because we want to add an item after this method
{
IList<TKey> 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<TKey, TValue> cache;
public CacheItem(MemoryCache<TKey, TValue> 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);
}
}
}
}