File: System\Caching\MemoryCache.cs
Project: ndp\fx\src\Caching\System.Runtime.Caching.csproj (System.Runtime.Caching)
// <copyright file="MemoryCache.cs" company="Microsoft">
//   Copyright (c) 2009 Microsoft Corporation.  All rights reserved.
// </copyright>
using System;
using System.Runtime.Caching.Configuration;
using System.Runtime.Caching.Resources;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
using System.Configuration;
using System.Diagnostics.CodeAnalysis;
using System.Security;
using System.Security.Permissions;
using System.Threading;
 
namespace System.Runtime.Caching {
    [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification= "The class represents a type of cache")]
    public class MemoryCache : ObjectCache, IEnumerable, IDisposable {
        private const DefaultCacheCapabilities CAPABILITIES = DefaultCacheCapabilities.InMemoryProvider
                                                              | DefaultCacheCapabilities.CacheEntryChangeMonitors
                                                              | DefaultCacheCapabilities.AbsoluteExpirations
                                                              | DefaultCacheCapabilities.SlidingExpirations
                                                              | DefaultCacheCapabilities.CacheEntryUpdateCallback
                                                              | DefaultCacheCapabilities.CacheEntryRemovedCallback;
        private static readonly TimeSpan OneYear = new TimeSpan(365, 0, 0, 0);
        private static object s_initLock = new object();
        private static MemoryCache s_defaultCache;
        private static CacheEntryRemovedCallback s_sentinelRemovedCallback = new CacheEntryRemovedCallback(SentinelEntry.OnCacheEntryRemovedCallback);
        private GCHandleRef<MemoryCacheStore>[] _storeRefs;
        private int _storeCount;
        private int _disposed;
        private MemoryCacheStatistics _stats;
        private string _name;
        private PerfCounters _perfCounters;
        private bool _configLess;
        private bool _useMemoryCacheManager = true;
        EventHandler _onAppDomainUnload;
        UnhandledExceptionEventHandler _onUnhandledException;
 
        private bool IsDisposed { get { return (_disposed == 1); } }
        internal bool ConfigLess { get { return _configLess; } }
 
        private class SentinelEntry {
            private string _key;
            private ChangeMonitor _expensiveObjectDependency;
            private CacheEntryUpdateCallback _updateCallback;
 
            internal SentinelEntry(string key, ChangeMonitor expensiveObjectDependency, CacheEntryUpdateCallback callback) {
                _key = key;
                _expensiveObjectDependency = expensiveObjectDependency;
                _updateCallback = callback;
            }
 
            internal string Key {
                get { return _key; }
            }
 
            internal ChangeMonitor ExpensiveObjectDependency {
                get { return _expensiveObjectDependency; }
            }
 
            internal CacheEntryUpdateCallback CacheEntryUpdateCallback {
                get { return _updateCallback; }
            }
 
            private static bool IsPolicyValid(CacheItemPolicy policy) {
                if (policy == null) {
                    return false;
                }
                // see if any change monitors have changed
                bool hasChanged = false;
                Collection<ChangeMonitor> changeMonitors = policy.ChangeMonitors;
                if (changeMonitors != null) {
                    foreach (ChangeMonitor monitor in changeMonitors) {
                        if (monitor != null && monitor.HasChanged) {
                            hasChanged = true;
                            break;
                        }
                    }
                }
                // if the monitors haven't changed yet and we have an update callback
                // then the policy is valid
                if (!hasChanged && policy.UpdateCallback != null) {
                    return true;
                }
                // if the monitors have changed we need to dispose them
                if (hasChanged) {
                    foreach (ChangeMonitor monitor in changeMonitors) {
                        if (monitor != null) {
                            monitor.Dispose();
                        }
                    }
                }
                return false;
            }
 
            internal static void OnCacheEntryRemovedCallback(CacheEntryRemovedArguments arguments) {
                MemoryCache cache = arguments.Source as MemoryCache;
                SentinelEntry entry = arguments.CacheItem.Value as SentinelEntry;
                CacheEntryRemovedReason reason = arguments.RemovedReason;
                switch (reason) {
                    case CacheEntryRemovedReason.Expired:
                        break;
                    case CacheEntryRemovedReason.ChangeMonitorChanged:
                        if (entry.ExpensiveObjectDependency.HasChanged) {
                            // If the expensiveObject has been removed explicitly by Cache.Remove,
                            // return from the SentinelEntry removed callback
                            // thus effectively removing the SentinelEntry from the cache.
                            return;
                        }
                        break;
                    case CacheEntryRemovedReason.Evicted:
                        Dbg.Fail("Reason should never be CacheEntryRemovedReason.Evicted since the entry was inserted as NotRemovable.");
                        return;
                    default:
                        // do nothing if reason is Removed or CacheSpecificEviction
                        return;
                }
 
                // invoke update callback
                try {
                    CacheEntryUpdateArguments args = new CacheEntryUpdateArguments(cache, reason, entry.Key, null);
                    entry.CacheEntryUpdateCallback(args);
                    Object expensiveObject = (args.UpdatedCacheItem != null) ? args.UpdatedCacheItem.Value : null;
                    CacheItemPolicy policy = args.UpdatedCacheItemPolicy;
                    // Dev10 861163 - Only update the "expensive" object if the user returns a new object,
                    // a policy with update callback, and the change monitors haven't changed.  (Inserting
                    // with change monitors that have already changed will cause recursion.)
                    if (expensiveObject != null && IsPolicyValid(policy)) {
                        cache.Set(entry.Key, expensiveObject, policy);
                    }
                    else {
                        cache.Remove(entry.Key);
                    }
                }
                catch {
                    cache.Remove(entry.Key);
                    // Review: What should we do with this exception?
                }
            }
        }
 
 
        // private and internal
 
        internal MemoryCacheStore GetStore(MemoryCacheKey cacheKey) {
            // Dev10 865907: Math.Abs throws OverflowException for Int32.MinValue
            int hashCode = cacheKey.Hash;
            if (hashCode < 0) {
                hashCode = (hashCode == Int32.MinValue) ? 0 : -hashCode;
            }
            int idx = hashCode % _storeCount;
            return _storeRefs[idx].Target;
        }
 
        internal object[] AllSRefTargets {
            get {
                var allStores = new MemoryCacheStore[_storeCount];
                for (int i = 0; i < _storeCount; i++) {
                    allStores[i] = _storeRefs[i].Target;
                }
                return allStores;
            }
        }
 
        [SecuritySafeCritical]
        [PermissionSet(SecurityAction.Assert, Unrestricted = true)]
        [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Grandfathered suppression from original caching code checkin")]
        private void InitDisposableMembers(NameValueCollection config) {
            bool dispose = true;
            try {
                try {
                    _perfCounters = new PerfCounters(_name);
                }
                catch {
                    // ignore exceptions from perf counters
                }
                for (int i = 0; i < _storeCount; i++) {
                    _storeRefs[i] = new GCHandleRef<MemoryCacheStore> (new MemoryCacheStore(this, _perfCounters));
                }
                _stats = new MemoryCacheStatistics(this, config);
                AppDomain appDomain = Thread.GetDomain();
                EventHandler onAppDomainUnload = new EventHandler(OnAppDomainUnload);
                appDomain.DomainUnload += onAppDomainUnload;
                _onAppDomainUnload = onAppDomainUnload;
                UnhandledExceptionEventHandler onUnhandledException = new UnhandledExceptionEventHandler(OnUnhandledException);
                appDomain.UnhandledException += onUnhandledException;
                _onUnhandledException = onUnhandledException;
                dispose = false;
            }
            finally {
                if (dispose) {
                    Dispose();
                }
            }
        }
 
        private void OnAppDomainUnload(Object unusedObject, EventArgs unusedEventArgs) {
            Dispose();
        }
 
        private void OnUnhandledException(Object sender, UnhandledExceptionEventArgs eventArgs) {
            // if the CLR is terminating, dispose the cache. 
            // This will dispose the perf counters (see Dev10 680819).
            if (eventArgs.IsTerminating) {
                Dispose();
            }
        }
 
        private void ValidatePolicy(CacheItemPolicy policy) {
            if (policy.AbsoluteExpiration != ObjectCache.InfiniteAbsoluteExpiration
                && policy.SlidingExpiration != ObjectCache.NoSlidingExpiration) {
                throw new ArgumentException(R.Invalid_expiration_combination, "policy");
            }            
            if (policy.SlidingExpiration < ObjectCache.NoSlidingExpiration || OneYear < policy.SlidingExpiration) {
                throw new ArgumentOutOfRangeException("policy", RH.Format(R.Argument_out_of_range, "SlidingExpiration", ObjectCache.NoSlidingExpiration, OneYear));
            }
            if (policy.RemovedCallback != null
                && policy.UpdateCallback != null) {
                throw new ArgumentException(R.Invalid_callback_combination, "policy");
            }
            if (policy.Priority != CacheItemPriority.Default && policy.Priority != CacheItemPriority.NotRemovable) {
                throw new ArgumentOutOfRangeException("policy", RH.Format(R.Argument_out_of_range, "Priority", CacheItemPriority.Default, CacheItemPriority.NotRemovable));
            }
        }
 
        // public
 
        // Amount of memory that can be used before
        // the cache begins to forcibly remove items.
        public long CacheMemoryLimit {
            get {
                return _stats.CacheMemoryLimit;
            }
        }
 
        public static MemoryCache Default { 
            get {
                if (s_defaultCache == null) {
                    lock (s_initLock) {
                        if (s_defaultCache == null) {
                            s_defaultCache = new MemoryCache();
                        }
                    }
                }
                return s_defaultCache;
            }
        }
 
        public override DefaultCacheCapabilities DefaultCacheCapabilities {
            get {
                return CAPABILITIES;
            }
        }
 
        public override string Name
        {
            get { return _name; }
        }
 
        internal bool UseMemoryCacheManager {
            get { return _useMemoryCacheManager; }
        }
 
        // Percentage of physical memory that can be used before
        // the cache begins to forcibly remove items.
        public long PhysicalMemoryLimit {
            get {
                return _stats.PhysicalMemoryLimit;
            }
        }
 
        // The maximum interval of time afterwhich the cache
        // will update its memory statistics.
        public TimeSpan PollingInterval {
            get {
                return _stats.PollingInterval;
            }
        }
 
        // Only used for Default MemoryCache
        private MemoryCache() {
            _name = "Default";
            Init(null);
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "This is assembly is a special case approved by the NetFx API review board")]
        public MemoryCache(string name, NameValueCollection config = null) {
            if (name == null) {
                throw new ArgumentNullException("name");
            }
            if (name == String.Empty) {
                throw new ArgumentException(R.Empty_string_invalid, "name");
            }
            if (String.Equals(name, "default", StringComparison.OrdinalIgnoreCase)) {
                throw new ArgumentException(R.Default_is_reserved, "name");
            }
            _name = name;
            Init(config);
        }
            
        // ignoreConfigSection is used when redirecting ASP.NET cache into the MemoryCache.  This avoids infinite recursion
        // due to the fact that the (ASP.NET) config system uses the cache, and the cache uses the
        // config system.  This method could be made public, perhaps with CAS to prevent partial trust callers.
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "This is assembly is a special case approved by the NetFx API review board")]
        public MemoryCache(string name, NameValueCollection config, bool ignoreConfigSection) {
            if (name == null) {
                throw new ArgumentNullException("name");
            }
            if (name == String.Empty) {
                throw new ArgumentException(R.Empty_string_invalid, "name");
            }
            if (String.Equals(name, "default", StringComparison.OrdinalIgnoreCase)) {
                throw new ArgumentException(R.Default_is_reserved, "name");
            }
            _name = name;
            _configLess = ignoreConfigSection;
            Init(config);
        }
 
        private void Init(NameValueCollection config) {
            _storeCount = Environment.ProcessorCount;
            _storeRefs = new GCHandleRef<MemoryCacheStore>[_storeCount];
            if (config != null) {
                _useMemoryCacheManager = ConfigUtil.GetBooleanValue(config, ConfigUtil.UseMemoryCacheManager, true);
            }
            InitDisposableMembers(config);
        }
 
        private object AddOrGetExistingInternal(string key, object value, CacheItemPolicy policy) {
            if (key == null) {
                throw new ArgumentNullException("key");
            }
            DateTimeOffset absExp = ObjectCache.InfiniteAbsoluteExpiration;
            TimeSpan slidingExp = ObjectCache.NoSlidingExpiration;
            CacheItemPriority priority = CacheItemPriority.Default;
            Collection<ChangeMonitor> changeMonitors = null;
            CacheEntryRemovedCallback removedCallback = null;
            if (policy != null) {
                ValidatePolicy(policy);
                if (policy.UpdateCallback != null) {
                    throw new ArgumentException(R.Update_callback_must_be_null, "policy");
                }
                absExp = policy.AbsoluteExpiration;
                slidingExp = policy.SlidingExpiration;
                priority = policy.Priority;
                changeMonitors = policy.ChangeMonitors;
                removedCallback = policy.RemovedCallback;
            }            
            if (IsDisposed) {
                if (changeMonitors != null) {
                    foreach (ChangeMonitor monitor in changeMonitors) {
                        if (monitor != null) {
                            monitor.Dispose();
                        }
                    }
                }
                return null;            
            }
            MemoryCacheKey cacheKey = new MemoryCacheKey(key);
            MemoryCacheStore store = GetStore(cacheKey);
            MemoryCacheEntry entry = store.AddOrGetExisting(cacheKey, new MemoryCacheEntry(key, value, absExp, slidingExp, priority, changeMonitors, removedCallback, this));
            return (entry != null) ? entry.Value : null;
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<String> keys, String regionName = null) {
            if (regionName != null) {
                throw new NotSupportedException(R.RegionName_not_supported);
            }
            if (keys == null) {
                throw new ArgumentNullException("keys");
            }
            List<String> keysClone = new List<String>(keys);
            if (keysClone.Count == 0) {
                throw new ArgumentException(RH.Format(R.Empty_collection, "keys"));
            }
 
            foreach (string key in keysClone) {
                if (key == null) {
                    throw new ArgumentException(RH.Format(R.Collection_contains_null_element, "keys"));
                }
            }
 
            return new MemoryCacheEntryChangeMonitor(keysClone.AsReadOnly(), regionName, this);
        }
 
        public void Dispose() {
            if (Interlocked.Exchange(ref _disposed, 1) == 0) {
                // unhook domain events
                DisposeSafeCritical();
                // stats must be disposed prior to disposing the stores.
                if (_stats != null) {
                    _stats.Dispose();
                }
                if (_storeRefs != null) {
                    foreach (var storeRef in _storeRefs) {
                        if (storeRef != null) {
                            storeRef.Dispose();
                        }
                    }
                }
                if (_perfCounters != null) {
                    _perfCounters.Dispose();
                }
                GC.SuppressFinalize(this);
            }
        }
 
        [SecuritySafeCritical]
        [PermissionSet(SecurityAction.Assert, Unrestricted = true)]
        [SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", Justification = "Grandfathered suppression from original caching code checkin")]
        private void DisposeSafeCritical() {
            AppDomain appDomain = Thread.GetDomain();
            if (_onAppDomainUnload != null) {
                appDomain.DomainUnload -= _onAppDomainUnload;
            }
            if (_onUnhandledException != null) {
                appDomain.UnhandledException -= _onUnhandledException;
            }
        }
 
        private object GetInternal(string key, string regionName) {
            if (regionName != null) {
                throw new NotSupportedException(R.RegionName_not_supported);
            }
            if (key == null) {
                throw new ArgumentNullException("key");
            }
            MemoryCacheEntry entry = GetEntry(key);
            return (entry != null) ? entry.Value : null;
        }
 
        internal MemoryCacheEntry GetEntry(String key) {
            if (IsDisposed) {
                return null;
            }
            MemoryCacheKey cacheKey = new MemoryCacheKey(key);
            MemoryCacheStore store = GetStore(cacheKey);
            return store.Get(cacheKey);
        }
 
        IEnumerator IEnumerable.GetEnumerator() {
            Hashtable h = new Hashtable();
            if (!IsDisposed) {
                foreach (var storeRef in _storeRefs) {
                    storeRef.Target.CopyTo(h);
                }
            }
            return h.GetEnumerator();
        }
 
        protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator() {
            Dictionary<string, object> h = new Dictionary<string, object>();
            if (!IsDisposed) {
                foreach (var storeRef in _storeRefs) {
                    storeRef.Target.CopyTo(h);
                }
            }
            return h.GetEnumerator();
        }
 
        internal MemoryCacheEntry RemoveEntry(string key, MemoryCacheEntry entry, CacheEntryRemovedReason reason) {
            MemoryCacheKey cacheKey = new MemoryCacheKey(key);
            MemoryCacheStore store = GetStore(cacheKey);
            return store.Remove(cacheKey, entry, reason);
        }
 
        public long Trim(int percent) {
            if (percent > 100) {
                percent = 100;
            }
            long trimmed = 0;
            if (_disposed == 0) {
                foreach (var storeRef in _storeRefs) {
                    trimmed += storeRef.Target.TrimInternal(percent);
                }
            }
            return trimmed;
        }
 
        //Default indexer property
        public override object this[string key] {
            get {
                return GetInternal(key, null);
            }
            set {
                Set(key, value, ObjectCache.InfiniteAbsoluteExpiration);
            }
        }
 
        //Existence check for a single item
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override bool Contains(string key, string regionName = null) {
            return (GetInternal(key, regionName) != null);
        }
 
        // Dev10 907758: Breaking bug in System.RuntimeCaching.MemoryCache.AddOrGetExisting (CacheItem, CacheItemPolicy)
        public override bool Add(CacheItem item, CacheItemPolicy policy) {
            CacheItem existingEntry = AddOrGetExisting(item, policy);
            return (existingEntry == null || existingEntry.Value == null);
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) {
            if (regionName != null) {
                throw new NotSupportedException(R.RegionName_not_supported);
            }
            CacheItemPolicy policy = new CacheItemPolicy();
            policy.AbsoluteExpiration = absoluteExpiration;
            return AddOrGetExistingInternal(key, value, policy);
        }
 
        public override CacheItem AddOrGetExisting(CacheItem item, CacheItemPolicy policy) {
            if (item == null) {
                throw new ArgumentNullException("item");
            }
            return new CacheItem(item.Key, AddOrGetExistingInternal(item.Key, item.Value, policy));
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null) {
            if (regionName != null) {
                throw new NotSupportedException(R.RegionName_not_supported);
            }
            return AddOrGetExistingInternal(key, value, policy);
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override object Get(string key, string regionName = null) {
            return GetInternal(key, regionName);
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override CacheItem GetCacheItem(string key, string regionName = null) {
            object value = GetInternal(key, regionName);
            return (value != null) ? new CacheItem(key, value) : null;
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) {
            if (regionName != null) {
                throw new NotSupportedException(R.RegionName_not_supported);
            }
            CacheItemPolicy policy = new CacheItemPolicy();
            policy.AbsoluteExpiration = absoluteExpiration;
            Set(key, value, policy);
        }
 
        public override void Set(CacheItem item, CacheItemPolicy policy) {
            if (item == null) {
                throw new ArgumentNullException("item");
            }
            Set(item.Key, item.Value, policy);
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) {
            if (regionName != null) {
                throw new NotSupportedException(R.RegionName_not_supported);
            }
            if (key == null) {
                throw new ArgumentNullException("key");
            }
            DateTimeOffset absExp = ObjectCache.InfiniteAbsoluteExpiration;
            TimeSpan slidingExp = ObjectCache.NoSlidingExpiration;
            CacheItemPriority priority = CacheItemPriority.Default;
            Collection<ChangeMonitor> changeMonitors = null;
            CacheEntryRemovedCallback removedCallback = null;
            if (policy != null) {
                ValidatePolicy(policy);
                if (policy.UpdateCallback != null) {
                    Set(key, value, policy.ChangeMonitors, policy.AbsoluteExpiration, policy.SlidingExpiration, policy.UpdateCallback);
                    return;
                }
                absExp = policy.AbsoluteExpiration;
                slidingExp = policy.SlidingExpiration;
                priority = policy.Priority;
                changeMonitors = policy.ChangeMonitors;
                removedCallback = policy.RemovedCallback;
            }
            if (IsDisposed) {
                if (changeMonitors != null) {
                    foreach (ChangeMonitor monitor in changeMonitors) {
                        if (monitor != null) {
                            monitor.Dispose();
                        }
                    }
                }
                return;
            }            
            MemoryCacheKey cacheKey = new MemoryCacheKey(key);
            MemoryCacheStore store = GetStore(cacheKey);
            store.Set(cacheKey, new MemoryCacheEntry(key, value, absExp, slidingExp, priority, changeMonitors, removedCallback, this));
        }
 
        // DevDiv Bugs 162763: 
        // Add a an event that fires *before* an item is evicted from the ASP.NET Cache
        internal void Set(string key, 
                          object value,
                          Collection<ChangeMonitor> changeMonitors,
                          DateTimeOffset absoluteExpiration,
                          TimeSpan slidingExpiration,
                          CacheEntryUpdateCallback onUpdateCallback) {
            if (key == null) {
                throw new ArgumentNullException("key");
            }
            if (changeMonitors == null
                && absoluteExpiration == ObjectCache.InfiniteAbsoluteExpiration 
                && slidingExpiration == ObjectCache.NoSlidingExpiration) {
                throw new ArgumentException(R.Invalid_argument_combination);
            }
            if (onUpdateCallback == null) {
                throw new ArgumentNullException("onUpdateCallback");
            }
            if (IsDisposed) {
                if (changeMonitors != null) {
                    foreach (ChangeMonitor monitor in changeMonitors) {
                        if (monitor != null) {
                            monitor.Dispose();
                        }
                    }
                }
                return;
            }
            // Insert updatable cache entry
            MemoryCacheKey cacheKey = new MemoryCacheKey(key);
            MemoryCacheStore store = GetStore(cacheKey);
            MemoryCacheEntry cacheEntry = new MemoryCacheEntry(key, 
                                                               value, 
                                                               ObjectCache.InfiniteAbsoluteExpiration, 
                                                               ObjectCache.NoSlidingExpiration, 
                                                               CacheItemPriority.NotRemovable, 
                                                               null,
                                                               null, 
                                                               this);
            store.Set(cacheKey, cacheEntry);
 
            // Ensure the sentinel depends on its updatable entry
            string[] cacheKeys = { key };
            ChangeMonitor expensiveObjectDep = CreateCacheEntryChangeMonitor(cacheKeys);
            if (changeMonitors == null) {
                changeMonitors = new Collection<ChangeMonitor>();
            }
            changeMonitors.Add(expensiveObjectDep);
 
            // Insert sentinel entry for the updatable cache entry 
            MemoryCacheKey sentinelCacheKey = new MemoryCacheKey("OnUpdateSentinel" + key);
            MemoryCacheStore sentinelStore = GetStore(sentinelCacheKey);
            MemoryCacheEntry sentinelCacheEntry = new MemoryCacheEntry(sentinelCacheKey.Key,
                                                                       new SentinelEntry(key, expensiveObjectDep, onUpdateCallback),
                                                                       absoluteExpiration, 
                                                                       slidingExpiration,
                                                                       CacheItemPriority.NotRemovable, 
                                                                       changeMonitors,
                                                                       s_sentinelRemovedCallback, 
                                                                       this);
            sentinelStore.Set(sentinelCacheKey, sentinelCacheEntry);
            cacheEntry.ConfigureUpdateSentinel(sentinelStore, sentinelCacheEntry);
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override object Remove(string key, string regionName = null) {
            return Remove(key, CacheEntryRemovedReason.Removed, regionName);
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification = "This is assembly is a special case approved by the NetFx API review board")]
        public object Remove(string key, CacheEntryRemovedReason reason, string regionName = null) {
            if (regionName != null) {
                throw new NotSupportedException(R.RegionName_not_supported);
            }
            if (key == null) {
                throw new ArgumentNullException("key");
            }
            if (IsDisposed) {
                return null;
            }
            MemoryCacheEntry entry = RemoveEntry(key, null, reason);
            return (entry != null) ? entry.Value : null;
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override long GetCount(string regionName = null) {
            if (regionName != null) {
                throw new NotSupportedException(R.RegionName_not_supported);
            }
            long count = 0;
            if (!IsDisposed) {
                foreach (var storeRef in _storeRefs) {
                    count += storeRef.Target.Count;
                }
            }
            return count;
        }
 
        public long GetLastSize(string regionName = null) {
            if (regionName != null) {
                throw new NotSupportedException(R.RegionName_not_supported);
            }
 
            return _stats.GetLastSize();
        }
 
        [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed", Justification="This is assembly is a special case approved by the NetFx API review board")]
        public override IDictionary<string, object> GetValues(IEnumerable<String> keys, string regionName = null) {
            if (regionName != null) {
                throw new NotSupportedException(R.RegionName_not_supported);
            }
            if (keys == null) {
                throw new ArgumentNullException("keys");
            }
            Dictionary<string, object> values = null;
            if (!IsDisposed) {
                foreach (string key in keys) {
                    if (key == null) {
                        throw new ArgumentException(RH.Format(R.Collection_contains_null_element, "keys"));
                    }
                    object value = GetInternal(key, null);
                    if (value != null) {
                        if (values == null) {
                            values = new Dictionary<string, object>();
                        }
                        values[key] = value;
                    }
                }
            }
            return values;
        }
 
        // used when redirecting ASP.NET cache into the MemoryCache.  This avoids infinite recursion
        // due to the fact that the (ASP.NET) config system uses the cache, and the cache uses the
        // config system.
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Grandfathered suppression from original caching code checkin")]
        internal void UpdateConfig(NameValueCollection config) {
            if (config == null) {
                throw new ArgumentNullException("config");
            }
            if (!IsDisposed) {
                _stats.UpdateConfig(config);
            }
        }
    }
}