File: System\Runtime\Caching\MemoryCache.cs
Web Access
Project: src\src\libraries\System.Runtime.Caching\src\System.Runtime.Caching.csproj (System.Runtime.Caching)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Configuration;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.Caching.Configuration;
using System.Runtime.Caching.Resources;
using System.Runtime.Versioning;
using System.Security;
using System.Threading;
 
namespace System.Runtime.Caching
{
    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 s_oneYear = new TimeSpan(365, 0, 0, 0);
        private static readonly object s_initLock = new object();
        private static MemoryCache s_defaultCache;
        private static readonly CacheEntryRemovedCallback s_sentinelRemovedCallback = new CacheEntryRemovedCallback(SentinelEntry.OnCacheEntryRemovedCallback);
        private GCHandleRef<MemoryCacheStore>[] _storeRefs;
        private int _storeCount;
        private int _disposed;
        private MemoryCacheStatistics _stats;
        private readonly string _name;
        private Counters _perfCounters;
        private readonly bool _configLess;
        private bool _useMemoryCacheManager = true;
        private bool _throwOnDisposed;
#if NET
        [UnsupportedOSPlatformGuard("wasi")]
        [UnsupportedOSPlatformGuard("browser")]
        private static bool _countersSupported => !OperatingSystem.IsBrowser() && !OperatingSystem.IsWasi();
#else
        private static bool _countersSupported => true;
#endif
 
        private bool IsDisposed { get { return (_disposed == 1); } }
        internal bool ConfigLess { get { return _configLess; } }
 
        private sealed class SentinelEntry
        {
            private readonly string _key;
            private readonly ChangeMonitor _expensiveObjectDependency;
            private readonly 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)
                    {
                        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:
                        Debug.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?.Value;
                    CacheItemPolicy policy = args.UpdatedCacheItemPolicy;
                    // 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)
        {
            int hashCode = cacheKey.Hash;
            if (hashCode < 0)
            {
                hashCode = (hashCode == int.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;
            }
        }
 
        private void InitDisposableMembers(NameValueCollection config)
        {
            bool dispose = true;
            try
            {
                try
                {
                    if (_countersSupported)
                    {
                        _perfCounters = new Counters(_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);
                dispose = false;
            }
            finally
            {
                if (dispose)
                {
                    Dispose();
                }
            }
        }
 
        private static void ValidatePolicy(CacheItemPolicy policy)
        {
            if (policy.AbsoluteExpiration != ObjectCache.InfiniteAbsoluteExpiration
                && policy.SlidingExpiration != ObjectCache.NoSlidingExpiration)
            {
                throw new ArgumentException(SR.Invalid_expiration_combination, nameof(policy));
            }
            if (policy.SlidingExpiration < ObjectCache.NoSlidingExpiration || s_oneYear < policy.SlidingExpiration)
            {
                throw new ArgumentOutOfRangeException(nameof(policy), RH.Format(SR.Argument_out_of_range, "SlidingExpiration", ObjectCache.NoSlidingExpiration, s_oneYear));
            }
            if (policy.RemovedCallback != null
                && policy.UpdateCallback != null)
            {
                throw new ArgumentException(SR.Invalid_callback_combination, nameof(policy));
            }
            if (policy.Priority != CacheItemPriority.Default && policy.Priority != CacheItemPriority.NotRemovable)
            {
                throw new ArgumentOutOfRangeException(nameof(policy), RH.Format(SR.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)
                    {
                        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 after which 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);
        }
 
        public MemoryCache(string name, NameValueCollection config = null)
        {
            if (name is null)
            {
                throw new ArgumentNullException(nameof(name));
            }
 
            if (name.Length == 0)
            {
                throw new ArgumentException(SR.Empty_string_invalid, nameof(name));
            }
            if (string.Equals(name, "default", StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException(SR.Default_is_reserved, nameof(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.
        public MemoryCache(string name, NameValueCollection config, bool ignoreConfigSection)
        {
            if (name is null)
            {
                throw new ArgumentNullException(nameof(name));
            }
 
            if (name.Length == 0)
            {
                throw new ArgumentException(SR.Empty_string_invalid, nameof(name));
            }
            if (string.Equals(name, "default", StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException(SR.Default_is_reserved, nameof(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);
                _throwOnDisposed = ConfigUtil.GetBooleanValue(config, ConfigUtil.ThrowOnDisposed, false);
            }
            InitDisposableMembers(config);
        }
 
        private object AddOrGetExistingInternal(string key, object value, CacheItemPolicy policy)
        {
            if (key is null)
            {
                throw new ArgumentNullException(nameof(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(SR.Update_callback_must_be_null, nameof(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)
                    {
                        monitor?.Dispose();
                    }
                }
 
                IsDisposedOrThrow();
 
                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?.Value;
        }
 
        public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> keys, string regionName = null)
        {
            if (regionName != null)
            {
                throw new NotSupportedException(SR.RegionName_not_supported);
            }
            if (keys == null)
            {
                throw new ArgumentNullException(nameof(keys));
            }
            List<string> keysClone = new List<string>(keys);
            if (keysClone.Count == 0)
            {
                throw new ArgumentException(RH.Format(SR.Empty_collection, nameof(keys)));
            }
 
            foreach (string key in keysClone)
            {
                if (key == null)
                {
                    throw new ArgumentException(RH.Format(SR.Collection_contains_null_element, nameof(keys)));
                }
            }
 
            return new MemoryCacheEntryChangeMonitor(keysClone.AsReadOnly(), regionName, this);
        }
 
        public void Dispose()
        {
            if (Interlocked.Exchange(ref _disposed, 1) == 0)
            {
                // stats must be disposed prior to disposing the stores.
                _stats?.Dispose();
                if (_storeRefs != null)
                {
                    foreach (var storeRef in _storeRefs)
                    {
                        storeRef?.Dispose();
                    }
                }
                if (_perfCounters != null)
                {
                    if (_countersSupported)
                    {
                        _perfCounters.Dispose();
                    }
                }
                GC.SuppressFinalize(this);
            }
        }
 
        private object GetInternal(string key, string regionName)
        {
            if (regionName != null)
            {
                throw new NotSupportedException(SR.RegionName_not_supported);
            }
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            MemoryCacheEntry entry = GetEntry(key);
            return entry?.Value;
        }
 
        internal MemoryCacheEntry GetEntry(string key)
        {
            if (IsDisposedOrThrow())
            {
                return null;
            }
            MemoryCacheKey cacheKey = new MemoryCacheKey(key);
            MemoryCacheStore store = GetStore(cacheKey);
            return store.Get(cacheKey);
        }
 
        IEnumerator IEnumerable.GetEnumerator()
        {
            Hashtable h = new Hashtable();
 
            if (!IsDisposedOrThrow())
            {
                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 (!IsDisposedOrThrow())
            {
                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)
        {
            long trimmed = 0;
            if (percent > 100)
            {
                percent = 100;
            }
 
            if (!IsDisposedOrThrow())
            {
                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
        public override bool Contains(string key, string regionName = null)
        {
            return (GetInternal(key, regionName) != null);
        }
 
        public override bool Add(CacheItem item, CacheItemPolicy policy)
        {
            CacheItem existingEntry = AddOrGetExisting(item, policy);
            return (existingEntry == null || existingEntry.Value == null);
        }
 
        public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
        {
            if (regionName != null)
            {
                throw new NotSupportedException(SR.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 is null)
            {
                throw new ArgumentNullException(nameof(item));
            }
 
            return new CacheItem(item.Key, AddOrGetExistingInternal(item.Key, item.Value, policy));
        }
 
        public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
        {
            if (regionName != null)
            {
                throw new NotSupportedException(SR.RegionName_not_supported);
            }
            return AddOrGetExistingInternal(key, value, policy);
        }
 
        public override object Get(string key, string regionName = null)
        {
            return GetInternal(key, regionName);
        }
 
        public override CacheItem GetCacheItem(string key, string regionName = null)
        {
            object value = GetInternal(key, regionName);
            return (value != null) ? new CacheItem(key, value) : null;
        }
 
        public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
        {
            if (regionName != null)
            {
                throw new NotSupportedException(SR.RegionName_not_supported);
            }
            CacheItemPolicy policy = new CacheItemPolicy();
            policy.AbsoluteExpiration = absoluteExpiration;
            Set(key, value, policy);
        }
 
        public override void Set(CacheItem item, CacheItemPolicy policy)
        {
            if (item is null)
            {
                throw new ArgumentNullException(nameof(item));
            }
 
            Set(item.Key, item.Value, policy);
        }
 
        public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
        {
            if (regionName != null)
            {
                throw new NotSupportedException(SR.RegionName_not_supported);
            }
            if (key == null)
            {
                throw new ArgumentNullException(nameof(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)
                    {
                        monitor?.Dispose();
                    }
                }
 
                IsDisposedOrThrow();
 
                return;
            }
            MemoryCacheKey cacheKey = new MemoryCacheKey(key);
            MemoryCacheStore store = GetStore(cacheKey);
            store.Set(cacheKey, new MemoryCacheEntry(key, value, absExp, slidingExp, priority, changeMonitors, removedCallback, this));
        }
 
        internal void Set(string key,
                          object value,
                          Collection<ChangeMonitor> changeMonitors,
                          DateTimeOffset absoluteExpiration,
                          TimeSpan slidingExpiration,
                          CacheEntryUpdateCallback onUpdateCallback)
        {
            if (key is null)
            {
                throw new ArgumentNullException(nameof(key));
            }
 
            if (changeMonitors == null
                && absoluteExpiration == ObjectCache.InfiniteAbsoluteExpiration
                && slidingExpiration == ObjectCache.NoSlidingExpiration)
            {
                throw new ArgumentException(SR.Invalid_argument_combination);
            }
            if (onUpdateCallback == null)
            {
                throw new ArgumentNullException(nameof(onUpdateCallback));
            }
            if (IsDisposed)
            {
                if (changeMonitors != null)
                {
                    foreach (ChangeMonitor monitor in changeMonitors)
                    {
                        monitor?.Dispose();
                    }
                }
 
                IsDisposedOrThrow();
 
                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);
            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);
        }
 
        public override object Remove(string key, string regionName = null)
        {
            return Remove(key, CacheEntryRemovedReason.Removed, regionName);
        }
 
        public object Remove(string key, CacheEntryRemovedReason reason, string regionName = null)
        {
            if (regionName != null)
            {
                throw new NotSupportedException(SR.RegionName_not_supported);
            }
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            if (IsDisposedOrThrow())
            {
                return null;
            }
            MemoryCacheEntry entry = RemoveEntry(key, null, reason);
            return entry?.Value;
        }
 
        public override long GetCount(string regionName = null)
        {
            if (regionName != null)
            {
                throw new NotSupportedException(SR.RegionName_not_supported);
            }
 
            long count = 0;
 
            if (!IsDisposedOrThrow())
            {
                foreach (var storeRef in _storeRefs)
                {
                    count += storeRef.Target.Count;
                }
            }
            return count;
        }
 
        public long GetLastSize(string regionName = null)
        {
            if (regionName != null)
            {
                throw new NotSupportedException(SR.RegionName_not_supported);
            }
 
            return _stats.GetLastSize();
        }
 
        public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName = null)
        {
            if (regionName != null)
            {
                throw new NotSupportedException(SR.RegionName_not_supported);
            }
            if (keys == null)
            {
                throw new ArgumentNullException(nameof(keys));
            }
 
            Dictionary<string, object> values = null;
 
            if (!IsDisposedOrThrow())
            {
                foreach (string key in keys)
                {
                    if (key == null)
                    {
                        throw new ArgumentException(RH.Format(SR.Collection_contains_null_element, nameof(keys)));
                    }
                    object value = GetInternal(key, null);
                    if (value != 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.
        internal void UpdateConfig(NameValueCollection config)
        {
            if (config is null)
            {
                throw new ArgumentNullException(nameof(config));
            }
 
            if (!IsDisposed)
            {
                _stats.UpdateConfig(config);
            }
        }
 
        private bool IsDisposedOrThrow()
        {
            if (!IsDisposed)
                return false;
 
            if (_throwOnDisposed)
            {
                string cacheName = $"{this.GetType().FullName}({_name})";
                throw new ObjectDisposedException(cacheName);
            }
 
            return true;
        }
    }
}