Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Runtime.Caching / System / Caching / MemoryCache.cs
1 // <copyright file="MemoryCache.cs" company="Microsoft">
2 //   Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
3 // </copyright>
4 using System;
5 using System.Runtime.Caching.Configuration;
6 using System.Runtime.Caching.Resources;
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.Collections.Specialized;
10 using System.Collections.ObjectModel;
11 using System.Configuration;
12 using System.Diagnostics.CodeAnalysis;
13 using System.Security;
14 using System.Security.Permissions;
15 using System.Threading;
16
17 namespace System.Runtime.Caching {
18     [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification= "The class represents a type of cache")]
19     public class MemoryCache : ObjectCache, IEnumerable, IDisposable {
20         private const DefaultCacheCapabilities CAPABILITIES = DefaultCacheCapabilities.InMemoryProvider
21                                                               | DefaultCacheCapabilities.CacheEntryChangeMonitors
22                                                               | DefaultCacheCapabilities.AbsoluteExpirations
23                                                               | DefaultCacheCapabilities.SlidingExpirations
24                                                               | DefaultCacheCapabilities.CacheEntryUpdateCallback
25                                                               | DefaultCacheCapabilities.CacheEntryRemovedCallback;
26         private static readonly TimeSpan OneYear = new TimeSpan(365, 0, 0, 0);
27         private static object s_initLock = new object();
28         private static MemoryCache s_defaultCache;
29         private static CacheEntryRemovedCallback s_sentinelRemovedCallback = new CacheEntryRemovedCallback(SentinelEntry.OnCacheEntryRemovedCallback);
30         private GCHandleRef<MemoryCacheStore>[] _storeRefs;
31         private int _storeCount;
32         private int _disposed;
33         private MemoryCacheStatistics _stats;
34         private string _name;
35         private PerfCounters _perfCounters;
36         private bool _configLess;
37         private bool _useMemoryCacheManager = true;
38         EventHandler _onAppDomainUnload;
39         UnhandledExceptionEventHandler _onUnhandledException;
40
41         private bool IsDisposed { get { return (_disposed == 1); } }
42         internal bool ConfigLess { get { return _configLess; } }
43
44         private class SentinelEntry {
45             private string _key;
46             private ChangeMonitor _expensiveObjectDependency;
47             private CacheEntryUpdateCallback _updateCallback;
48
49             internal SentinelEntry(string key, ChangeMonitor expensiveObjectDependency, CacheEntryUpdateCallback callback) {
50                 _key = key;
51                 _expensiveObjectDependency = expensiveObjectDependency;
52                 _updateCallback = callback;
53             }
54
55             internal string Key {
56                 get { return _key; }
57             }
58
59             internal ChangeMonitor ExpensiveObjectDependency {
60                 get { return _expensiveObjectDependency; }
61             }
62
63             internal CacheEntryUpdateCallback CacheEntryUpdateCallback {
64                 get { return _updateCallback; }
65             }
66
67             private static bool IsPolicyValid(CacheItemPolicy policy) {
68                 if (policy == null) {
69                     return false;
70                 }
71                 // see if any change monitors have changed
72                 bool hasChanged = false;
73                 Collection<ChangeMonitor> changeMonitors = policy.ChangeMonitors;
74                 if (changeMonitors != null) {
75                     foreach (ChangeMonitor monitor in changeMonitors) {
76                         if (monitor != null && monitor.HasChanged) {
77                             hasChanged = true;
78                             break;
79                         }
80                     }
81                 }
82                 // if the monitors haven't changed yet and we have an update callback
83                 // then the policy is valid
84                 if (!hasChanged && policy.UpdateCallback != null) {
85                     return true;
86                 }
87                 // if the monitors have changed we need to dispose them
88                 if (hasChanged) {
89                     foreach (ChangeMonitor monitor in changeMonitors) {
90                         if (monitor != null) {
91                             monitor.Dispose();
92                         }
93                     }
94                 }
95                 return false;
96             }
97
98             internal static void OnCacheEntryRemovedCallback(CacheEntryRemovedArguments arguments) {
99                 MemoryCache cache = arguments.Source as MemoryCache;
100                 SentinelEntry entry = arguments.CacheItem.Value as SentinelEntry;
101                 CacheEntryRemovedReason reason = arguments.RemovedReason;
102                 switch (reason) {
103                     case CacheEntryRemovedReason.Expired:
104                         break;
105                     case CacheEntryRemovedReason.ChangeMonitorChanged:
106                         if (entry.ExpensiveObjectDependency.HasChanged) {
107                             // If the expensiveObject has been removed explicitly by Cache.Remove,
108                             // return from the SentinelEntry removed callback
109                             // thus effectively removing the SentinelEntry from the cache.
110                             return;
111                         }
112                         break;
113                     case CacheEntryRemovedReason.Evicted:
114                         Dbg.Fail("Reason should never be CacheEntryRemovedReason.Evicted since the entry was inserted as NotRemovable.");
115                         return;
116                     default:
117                         // do nothing if reason is Removed or CacheSpecificEviction
118                         return;
119                 }
120
121                 // invoke update callback
122                 try {
123                     CacheEntryUpdateArguments args = new CacheEntryUpdateArguments(cache, reason, entry.Key, null);
124                     entry.CacheEntryUpdateCallback(args);
125                     Object expensiveObject = (args.UpdatedCacheItem != null) ? args.UpdatedCacheItem.Value : null;
126                     CacheItemPolicy policy = args.UpdatedCacheItemPolicy;
127                     // Dev10 861163 - Only update the "expensive" object if the user returns a new object,
128                     // a policy with update callback, and the change monitors haven't changed.  (Inserting
129                     // with change monitors that have already changed will cause recursion.)
130                     if (expensiveObject != null && IsPolicyValid(policy)) {
131                         cache.Set(entry.Key, expensiveObject, policy);
132                     }
133                     else {
134                         cache.Remove(entry.Key);
135                     }
136                 }
137                 catch {
138                     cache.Remove(entry.Key);
139                     // Review: What should we do with this exception?
140                 }
141             }
142         }
143
144
145         // private and internal
146
147         internal MemoryCacheStore GetStore(MemoryCacheKey cacheKey) {
148             // Dev10 865907: Math.Abs throws OverflowException for Int32.MinValue
149             int hashCode = cacheKey.Hash;
150             if (hashCode < 0) {
151                 hashCode = (hashCode == Int32.MinValue) ? 0 : -hashCode;
152             }
153             int idx = hashCode % _storeCount;
154             return _storeRefs[idx].Target;
155         }
156
157         internal object[] AllSRefTargets {
158             get {
159                 var allStores = new MemoryCacheStore[_storeCount];
160                 for (int i = 0; i < _storeCount; i++) {
161                     allStores[i] = _storeRefs[i].Target;
162                 }
163                 return allStores;
164             }
165         }
166
167         [SecuritySafeCritical]
168         [PermissionSet(SecurityAction.Assert, Unrestricted = true)]
169         [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Grandfathered suppression from original caching code checkin")]
170         private void InitDisposableMembers(NameValueCollection config) {
171             bool dispose = true;
172             try {
173                 try {
174                     _perfCounters = new PerfCounters(_name);
175                 }
176                 catch {
177                     // ignore exceptions from perf counters
178                 }
179                 for (int i = 0; i < _storeCount; i++) {
180                     _storeRefs[i] = new GCHandleRef<MemoryCacheStore> (new MemoryCacheStore(this, _perfCounters));
181                 }
182                 _stats = new MemoryCacheStatistics(this, config);
183                 AppDomain appDomain = Thread.GetDomain();
184                 EventHandler onAppDomainUnload = new EventHandler(OnAppDomainUnload);
185                 appDomain.DomainUnload += onAppDomainUnload;
186                 _onAppDomainUnload = onAppDomainUnload;
187                 UnhandledExceptionEventHandler onUnhandledException = new UnhandledExceptionEventHandler(OnUnhandledException);
188                 appDomain.UnhandledException += onUnhandledException;
189                 _onUnhandledException = onUnhandledException;
190                 dispose = false;
191             }
192             finally {
193                 if (dispose) {
194                     Dispose();
195                 }
196             }
197         }
198
199         private void OnAppDomainUnload(Object unusedObject, EventArgs unusedEventArgs) {
200             Dispose();
201         }
202
203         private void OnUnhandledException(Object sender, UnhandledExceptionEventArgs eventArgs) {
204             // if the CLR is terminating, dispose the cache. 
205             // This will dispose the perf counters (see Dev10 680819).
206             if (eventArgs.IsTerminating) {
207                 Dispose();
208             }
209         }
210
211         private void ValidatePolicy(CacheItemPolicy policy) {
212             if (policy.AbsoluteExpiration != ObjectCache.InfiniteAbsoluteExpiration
213                 && policy.SlidingExpiration != ObjectCache.NoSlidingExpiration) {
214                 throw new ArgumentException(R.Invalid_expiration_combination, "policy");
215             }            
216             if (policy.SlidingExpiration < ObjectCache.NoSlidingExpiration || OneYear < policy.SlidingExpiration) {
217                 throw new ArgumentOutOfRangeException("policy", RH.Format(R.Argument_out_of_range, "SlidingExpiration", ObjectCache.NoSlidingExpiration, OneYear));
218             }
219             if (policy.RemovedCallback != null
220                 && policy.UpdateCallback != null) {
221                 throw new ArgumentException(R.Invalid_callback_combination, "policy");
222             }
223             if (policy.Priority != CacheItemPriority.Default && policy.Priority != CacheItemPriority.NotRemovable) {
224                 throw new ArgumentOutOfRangeException("policy", RH.Format(R.Argument_out_of_range, "Priority", CacheItemPriority.Default, CacheItemPriority.NotRemovable));
225             }
226         }
227
228         // public
229
230         // Amount of memory that can be used before
231         // the cache begins to forcibly remove items.
232         public long CacheMemoryLimit {
233             get {
234                 return _stats.CacheMemoryLimit;
235             }
236         }
237
238         public static MemoryCache Default { 
239             get {
240                 if (s_defaultCache == null) {
241                     lock (s_initLock) {
242                         if (s_defaultCache == null) {
243                             s_defaultCache = new MemoryCache();
244                         }
245                     }
246                 }
247                 return s_defaultCache;
248             }
249         }
250
251         public override DefaultCacheCapabilities DefaultCacheCapabilities {
252             get {
253                 return CAPABILITIES;
254             }
255         }
256
257         public override string Name
258         {
259             get { return _name; }
260         }
261
262         internal bool UseMemoryCacheManager {
263             get { return _useMemoryCacheManager; }
264         }
265
266         // Percentage of physical memory that can be used before
267         // the cache begins to forcibly remove items.
268         public long PhysicalMemoryLimit {
269             get {
270                 return _stats.PhysicalMemoryLimit;
271             }
272         }
273
274         // The maximum interval of time afterwhich the cache
275         // will update its memory statistics.
276         public TimeSpan PollingInterval {
277             get {
278                 return _stats.PollingInterval;
279             }
280         }
281
282         // Only used for Default MemoryCache
283         private MemoryCache() {
284             _name = "Default";
285             Init(null);
286         }
287
288         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "This is assembly is a special case approved by the NetFx API review board")]
289         public MemoryCache(string name, NameValueCollection config = null) {
290             if (name == null) {
291                 throw new ArgumentNullException("name");
292             }
293             if (name == String.Empty) {
294                 throw new ArgumentException(R.Empty_string_invalid, "name");
295             }
296             if (String.Equals(name, "default", StringComparison.OrdinalIgnoreCase)) {
297                 throw new ArgumentException(R.Default_is_reserved, "name");
298             }
299             _name = name;
300             Init(config);
301         }
302             
303         // ignoreConfigSection is used when redirecting ASP.NET cache into the MemoryCache.  This avoids infinite recursion
304         // due to the fact that the (ASP.NET) config system uses the cache, and the cache uses the
305         // config system.  This method could be made public, perhaps with CAS to prevent partial trust callers.
306         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "This is assembly is a special case approved by the NetFx API review board")]
307         public MemoryCache(string name, NameValueCollection config, bool ignoreConfigSection) {
308             if (name == null) {
309                 throw new ArgumentNullException("name");
310             }
311             if (name == String.Empty) {
312                 throw new ArgumentException(R.Empty_string_invalid, "name");
313             }
314             if (String.Equals(name, "default", StringComparison.OrdinalIgnoreCase)) {
315                 throw new ArgumentException(R.Default_is_reserved, "name");
316             }
317             _name = name;
318             _configLess = ignoreConfigSection;
319             Init(config);
320         }
321
322
323         private void Init(NameValueCollection config) {
324             _storeCount = Environment.ProcessorCount;
325             if (config != null) {
326 #if MONO
327                 if (config ["__MonoEmulateOneCPU"] == "true")
328                     _storeCount = 1;
329                 if (config ["__MonoTimerPeriod"] != null) {
330                     try {
331                         int parsed = (int)UInt32.Parse (config ["__MonoTimerPeriod"]);
332                         CacheExpires.EXPIRATIONS_INTERVAL = new TimeSpan (0, 0, parsed);
333                     } catch {
334                         //
335                     }
336                 }
337 #endif                
338                 _useMemoryCacheManager = ConfigUtil.GetBooleanValue(config, ConfigUtil.UseMemoryCacheManager, true);
339             }
340             _storeRefs = new GCHandleRef<MemoryCacheStore>[_storeCount];
341
342             InitDisposableMembers(config);
343         }
344
345         private object AddOrGetExistingInternal(string key, object value, CacheItemPolicy policy) {
346             if (key == null) {
347                 throw new ArgumentNullException("key");
348             }
349             DateTimeOffset absExp = ObjectCache.InfiniteAbsoluteExpiration;
350             TimeSpan slidingExp = ObjectCache.NoSlidingExpiration;
351             CacheItemPriority priority = CacheItemPriority.Default;
352             Collection<ChangeMonitor> changeMonitors = null;
353             CacheEntryRemovedCallback removedCallback = null;
354             if (policy != null) {
355                 ValidatePolicy(policy);
356                 if (policy.UpdateCallback != null) {
357                     throw new ArgumentException(R.Update_callback_must_be_null, "policy");
358                 }
359                 absExp = policy.AbsoluteExpiration;
360                 slidingExp = policy.SlidingExpiration;
361                 priority = policy.Priority;
362                 changeMonitors = policy.ChangeMonitors;
363                 removedCallback = policy.RemovedCallback;
364             }            
365             if (IsDisposed) {
366                 if (changeMonitors != null) {
367                     foreach (ChangeMonitor monitor in changeMonitors) {
368                         if (monitor != null) {
369                             monitor.Dispose();
370                         }
371                     }
372                 }
373                 return null;            
374             }
375             MemoryCacheKey cacheKey = new MemoryCacheKey(key);
376             MemoryCacheStore store = GetStore(cacheKey);
377             MemoryCacheEntry entry = store.AddOrGetExisting(cacheKey, new MemoryCacheEntry(key, value, absExp, slidingExp, priority, changeMonitors, removedCallback, this));
378             return (entry != null) ? entry.Value : null;
379         }
380
381         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
382         public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<String> keys, String regionName = null) {
383             if (regionName != null) {
384                 throw new NotSupportedException(R.RegionName_not_supported);
385             }
386             if (keys == null) {
387                 throw new ArgumentNullException("keys");
388             }
389             List<String> keysClone = new List<String>(keys);
390             if (keysClone.Count == 0) {
391                 throw new ArgumentException(RH.Format(R.Empty_collection, "keys"));
392             }
393
394             foreach (string key in keysClone) {
395                 if (key == null) {
396                     throw new ArgumentException(RH.Format(R.Collection_contains_null_element, "keys"));
397                 }
398             }
399
400             return new MemoryCacheEntryChangeMonitor(keysClone.AsReadOnly(), regionName, this);
401         }
402
403         public void Dispose() {
404             if (Interlocked.Exchange(ref _disposed, 1) == 0) {
405                 // unhook domain events
406                 DisposeSafeCritical();
407                 // stats must be disposed prior to disposing the stores.
408                 if (_stats != null) {
409                     _stats.Dispose();
410                 }
411                 if (_storeRefs != null) {
412                     foreach (var storeRef in _storeRefs) {
413                         if (storeRef != null) {
414                             storeRef.Dispose();
415                         }
416                     }
417                 }
418                 if (_perfCounters != null) {
419                     _perfCounters.Dispose();
420                 }
421                 GC.SuppressFinalize(this);
422             }
423         }
424
425         [SecuritySafeCritical]
426         [PermissionSet(SecurityAction.Assert, Unrestricted = true)]
427         [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Grandfathered suppression from original caching code checkin")]
428         private void DisposeSafeCritical() {
429             AppDomain appDomain = Thread.GetDomain();
430             if (_onAppDomainUnload != null) {
431                 appDomain.DomainUnload -= _onAppDomainUnload;
432             }
433             if (_onUnhandledException != null) {
434                 appDomain.UnhandledException -= _onUnhandledException;
435             }
436         }
437
438         private object GetInternal(string key, string regionName) {
439             if (regionName != null) {
440                 throw new NotSupportedException(R.RegionName_not_supported);
441             }
442             if (key == null) {
443                 throw new ArgumentNullException("key");
444             }
445             MemoryCacheEntry entry = GetEntry(key);
446             return (entry != null) ? entry.Value : null;
447         }
448
449         internal MemoryCacheEntry GetEntry(String key) {
450             if (IsDisposed) {
451                 return null;
452             }
453             MemoryCacheKey cacheKey = new MemoryCacheKey(key);
454             MemoryCacheStore store = GetStore(cacheKey);
455             return store.Get(cacheKey);
456         }
457
458         IEnumerator IEnumerable.GetEnumerator() {
459             Hashtable h = new Hashtable();
460             if (!IsDisposed) {
461                 foreach (var storeRef in _storeRefs) {
462                     storeRef.Target.CopyTo(h);
463                 }
464             }
465             return h.GetEnumerator();
466         }
467
468         protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
469             Dictionary<string, object> h = new Dictionary<string, object>();
470             if (!IsDisposed) {
471                 foreach (var storeRef in _storeRefs) {
472                     storeRef.Target.CopyTo(h);
473                 }
474             }
475             return h.GetEnumerator();
476         }
477
478         internal MemoryCacheEntry RemoveEntry(string key, MemoryCacheEntry entry, CacheEntryRemovedReason reason) {
479             MemoryCacheKey cacheKey = new MemoryCacheKey(key);
480             MemoryCacheStore store = GetStore(cacheKey);
481             return store.Remove(cacheKey, entry, reason);
482         }
483
484         public long Trim(int percent) {
485             if (percent > 100) {
486                 percent = 100;
487             }
488             long trimmed = 0;
489             if (_disposed == 0) {
490                 foreach (var storeRef in _storeRefs) {
491                     trimmed += storeRef.Target.TrimInternal(percent);
492                 }
493             }
494             return trimmed;
495         }
496
497         //Default indexer property
498         public override object this[string key] {
499             get {
500                 return GetInternal(key, null);
501             }
502             set {
503                 Set(key, value, ObjectCache.InfiniteAbsoluteExpiration);
504             }
505         }
506
507         //Existence check for a single item
508         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
509         public override bool Contains(string key, string regionName = null) {
510             return (GetInternal(key, regionName) != null);
511         }
512
513         // Dev10 907758: Breaking bug in System.RuntimeCaching.MemoryCache.AddOrGetExisting (CacheItem, CacheItemPolicy)
514         public override bool Add(CacheItem item, CacheItemPolicy policy) {
515             CacheItem existingEntry = AddOrGetExisting(item, policy);
516             return (existingEntry == null || existingEntry.Value == null);
517         }
518
519         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
520         public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) {
521             if (regionName != null) {
522                 throw new NotSupportedException(R.RegionName_not_supported);
523             }
524             CacheItemPolicy policy = new CacheItemPolicy();
525             policy.AbsoluteExpiration = absoluteExpiration;
526             return AddOrGetExistingInternal(key, value, policy);
527         }
528
529         public override CacheItem AddOrGetExisting(CacheItem item, CacheItemPolicy policy) {
530             if (item == null) {
531                 throw new ArgumentNullException("item");
532             }
533             return new CacheItem(item.Key, AddOrGetExistingInternal(item.Key, item.Value, policy));
534         }
535
536         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
537         public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null) {
538             if (regionName != null) {
539                 throw new NotSupportedException(R.RegionName_not_supported);
540             }
541             return AddOrGetExistingInternal(key, value, policy);
542         }
543
544         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
545         public override object Get(string key, string regionName = null) {
546             return GetInternal(key, regionName);
547         }
548
549         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
550         public override CacheItem GetCacheItem(string key, string regionName = null) {
551             object value = GetInternal(key, regionName);
552             return (value != null) ? new CacheItem(key, value) : null;
553         }
554
555         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
556         public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) {
557             if (regionName != null) {
558                 throw new NotSupportedException(R.RegionName_not_supported);
559             }
560             CacheItemPolicy policy = new CacheItemPolicy();
561             policy.AbsoluteExpiration = absoluteExpiration;
562             Set(key, value, policy);
563         }
564
565         public override void Set(CacheItem item, CacheItemPolicy policy) {
566             if (item == null) {
567                 throw new ArgumentNullException("item");
568             }
569             Set(item.Key, item.Value, policy);
570         }
571
572         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
573         public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) {
574             if (regionName != null) {
575                 throw new NotSupportedException(R.RegionName_not_supported);
576             }
577             if (key == null) {
578                 throw new ArgumentNullException("key");
579             }
580             DateTimeOffset absExp = ObjectCache.InfiniteAbsoluteExpiration;
581             TimeSpan slidingExp = ObjectCache.NoSlidingExpiration;
582             CacheItemPriority priority = CacheItemPriority.Default;
583             Collection<ChangeMonitor> changeMonitors = null;
584             CacheEntryRemovedCallback removedCallback = null;
585             if (policy != null) {
586                 ValidatePolicy(policy);
587                 if (policy.UpdateCallback != null) {
588                     Set(key, value, policy.ChangeMonitors, policy.AbsoluteExpiration, policy.SlidingExpiration, policy.UpdateCallback);
589                     return;
590                 }
591                 absExp = policy.AbsoluteExpiration;
592                 slidingExp = policy.SlidingExpiration;
593                 priority = policy.Priority;
594                 changeMonitors = policy.ChangeMonitors;
595                 removedCallback = policy.RemovedCallback;
596             }
597             if (IsDisposed) {
598                 if (changeMonitors != null) {
599                     foreach (ChangeMonitor monitor in changeMonitors) {
600                         if (monitor != null) {
601                             monitor.Dispose();
602                         }
603                     }
604                 }
605                 return;
606             }            
607             MemoryCacheKey cacheKey = new MemoryCacheKey(key);
608             MemoryCacheStore store = GetStore(cacheKey);
609             store.Set(cacheKey, new MemoryCacheEntry(key, value, absExp, slidingExp, priority, changeMonitors, removedCallback, this));
610         }
611
612         // DevDiv Bugs 162763: 
613         // Add a an event that fires *before* an item is evicted from the ASP.NET Cache
614         internal void Set(string key, 
615                           object value,
616                           Collection<ChangeMonitor> changeMonitors,
617                           DateTimeOffset absoluteExpiration,
618                           TimeSpan slidingExpiration,
619                           CacheEntryUpdateCallback onUpdateCallback) {
620             if (key == null) {
621                 throw new ArgumentNullException("key");
622             }
623             if (changeMonitors == null
624                 && absoluteExpiration == ObjectCache.InfiniteAbsoluteExpiration 
625                 && slidingExpiration == ObjectCache.NoSlidingExpiration) {
626                 throw new ArgumentException(R.Invalid_argument_combination);
627             }
628             if (onUpdateCallback == null) {
629                 throw new ArgumentNullException("onUpdateCallback");
630             }
631             if (IsDisposed) {
632                 if (changeMonitors != null) {
633                     foreach (ChangeMonitor monitor in changeMonitors) {
634                         if (monitor != null) {
635                             monitor.Dispose();
636                         }
637                     }
638                 }
639                 return;
640             }
641             // Insert updatable cache entry
642             MemoryCacheKey cacheKey = new MemoryCacheKey(key);
643             MemoryCacheStore store = GetStore(cacheKey);
644             MemoryCacheEntry cacheEntry = new MemoryCacheEntry(key, 
645                                                                value, 
646                                                                ObjectCache.InfiniteAbsoluteExpiration, 
647                                                                ObjectCache.NoSlidingExpiration, 
648                                                                CacheItemPriority.NotRemovable, 
649                                                                null,
650                                                                null, 
651                                                                this);
652             store.Set(cacheKey, cacheEntry);
653
654             // Ensure the sentinel depends on its updatable entry
655             string[] cacheKeys = { key };
656             ChangeMonitor expensiveObjectDep = CreateCacheEntryChangeMonitor(cacheKeys);
657             if (changeMonitors == null) {
658                 changeMonitors = new Collection<ChangeMonitor>();
659             }
660             changeMonitors.Add(expensiveObjectDep);
661
662             // Insert sentinel entry for the updatable cache entry 
663             MemoryCacheKey sentinelCacheKey = new MemoryCacheKey("OnUpdateSentinel" + key);
664             MemoryCacheStore sentinelStore = GetStore(sentinelCacheKey);
665             MemoryCacheEntry sentinelCacheEntry = new MemoryCacheEntry(sentinelCacheKey.Key,
666                                                                        new SentinelEntry(key, expensiveObjectDep, onUpdateCallback),
667                                                                        absoluteExpiration, 
668                                                                        slidingExpiration,
669                                                                        CacheItemPriority.NotRemovable, 
670                                                                        changeMonitors,
671                                                                        s_sentinelRemovedCallback, 
672                                                                        this);
673             sentinelStore.Set(sentinelCacheKey, sentinelCacheEntry);
674             cacheEntry.ConfigureUpdateSentinel(sentinelStore, sentinelCacheEntry);
675         }
676
677         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
678         public override object Remove(string key, string regionName = null) {
679             return Remove(key, CacheEntryRemovedReason.Removed, regionName);
680         }
681
682         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "This is assembly is a special case approved by the NetFx API review board")]
683         public object Remove(string key, CacheEntryRemovedReason reason, string regionName = null) {
684             if (regionName != null) {
685                 throw new NotSupportedException(R.RegionName_not_supported);
686             }
687             if (key == null) {
688                 throw new ArgumentNullException("key");
689             }
690             if (IsDisposed) {
691                 return null;
692             }
693             MemoryCacheEntry entry = RemoveEntry(key, null, reason);
694             return (entry != null) ? entry.Value : null;
695         }
696
697         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
698         public override long GetCount(string regionName = null) {
699             if (regionName != null) {
700                 throw new NotSupportedException(R.RegionName_not_supported);
701             }
702             long count = 0;
703             if (!IsDisposed) {
704                 foreach (var storeRef in _storeRefs) {
705                     count += storeRef.Target.Count;
706                 }
707             }
708             return count;
709         }
710
711         public long GetLastSize(string regionName = null) {
712             if (regionName != null) {
713                 throw new NotSupportedException(R.RegionName_not_supported);
714             }
715
716             return _stats.GetLastSize();
717         }
718
719         [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
720         public override IDictionary<string, object> GetValues(IEnumerable<String> keys, string regionName = null) {
721             if (regionName != null) {
722                 throw new NotSupportedException(R.RegionName_not_supported);
723             }
724             if (keys == null) {
725                 throw new ArgumentNullException("keys");
726             }
727             Dictionary<string, object> values = null;
728             if (!IsDisposed) {
729                 foreach (string key in keys) {
730                     if (key == null) {
731                         throw new ArgumentException(RH.Format(R.Collection_contains_null_element, "keys"));
732                     }
733                     object value = GetInternal(key, null);
734                     if (value != null) {
735                         if (values == null) {
736                             values = new Dictionary<string, object>();
737                         }
738                         values[key] = value;
739                     }
740                 }
741             }
742             return values;
743         }
744
745         // used when redirecting ASP.NET cache into the MemoryCache.  This avoids infinite recursion
746         // due to the fact that the (ASP.NET) config system uses the cache, and the cache uses the
747         // config system.
748         [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Grandfathered suppression from original caching code checkin")]
749         internal void UpdateConfig(NameValueCollection config) {
750             if (config == null) {
751                 throw new ArgumentNullException("config");
752             }
753             if (!IsDisposed) {
754                 _stats.UpdateConfig(config);
755             }
756         }
757     }
758 }