File: System\Runtime\Caching\MemoryCacheEntryChangeMonitor.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.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.Caching.Resources;
using System.Text;
 
namespace System.Runtime.Caching
{
    internal sealed class MemoryCacheEntryChangeMonitor : CacheEntryChangeMonitor
    {
        // use UTC minimum DateTime for error free conversions to DateTimeOffset
        private static readonly DateTime s_DATETIME_MINVALUE_UTC = new DateTime(0, DateTimeKind.Utc);
        private const int MAX_CHAR_COUNT_OF_LONG_CONVERTED_TO_HEXADECIMAL_STRING = 16;
        private readonly ReadOnlyCollection<string> _keys;
        private readonly string _regionName;
        private string _uniqueId;
        private DateTimeOffset _lastModified;
        private List<MemoryCacheEntry> _dependencies;
 
        private MemoryCacheEntryChangeMonitor() { } // hide default .ctor
 
        private void InitDisposableMembers(MemoryCache cache)
        {
            bool dispose = true;
            try
            {
                bool hasChanged = false;
                string uniqueId = null;
                _dependencies = new List<MemoryCacheEntry>(_keys.Count);
                if (_keys.Count == 1)
                {
                    string k = _keys[0];
                    MemoryCacheEntry entry = cache.GetEntry(k);
                    DateTime utcCreated = s_DATETIME_MINVALUE_UTC;
                    StartMonitoring(cache, entry, ref hasChanged, ref utcCreated);
                    uniqueId = $"{k}{utcCreated.Ticks:X}";
                    _lastModified = utcCreated;
                }
                else
                {
                    int capacity = 0;
                    foreach (string key in _keys)
                    {
                        capacity += key.Length + MAX_CHAR_COUNT_OF_LONG_CONVERTED_TO_HEXADECIMAL_STRING;
                    }
                    StringBuilder sb = new StringBuilder(capacity);
                    foreach (string key in _keys)
                    {
                        MemoryCacheEntry entry = cache.GetEntry(key);
                        DateTime utcCreated = s_DATETIME_MINVALUE_UTC;
                        StartMonitoring(cache, entry, ref hasChanged, ref utcCreated);
                        sb.Append(key);
                        sb.Append($"{utcCreated.Ticks:X}");
                        if (utcCreated > _lastModified)
                        {
                            _lastModified = utcCreated;
                        }
                    }
                    uniqueId = sb.ToString();
                }
                _uniqueId = uniqueId;
                if (hasChanged)
                {
                    OnChanged(null);
                }
                dispose = false;
            }
            finally
            {
                InitializationComplete();
                if (dispose)
                {
                    Dispose();
                }
            }
        }
 
        private void StartMonitoring(MemoryCache cache, MemoryCacheEntry entry, ref bool hasChanged, ref DateTime utcCreated)
        {
            if (entry != null)
            {
                // pass reference to self so the dependency can notify us when it changes
                entry.AddDependent(cache, this);
                // add dependency to collection so we can dispose it later
                _dependencies.Add(entry);
                // has the entry already changed?
                if (entry.State != EntryState.AddedToCache)
                {
                    hasChanged = true;
                }
                utcCreated = entry.UtcCreated;
            }
            else
            {
                // the entry does not exist--set hasChanged to true so the user is notified
                hasChanged = true;
            }
        }
 
        //
        // protected members
        //
 
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (_dependencies != null)
                {
                    foreach (MemoryCacheEntry entry in _dependencies)
                    {
                        entry?.RemoveDependent(this);
                    }
                }
            }
        }
 
        //
        // public and internal members
        //
 
        public override ReadOnlyCollection<string> CacheKeys { get { return new ReadOnlyCollection<string>(_keys); } }
        public override string RegionName { get { return _regionName; } }
        public override string UniqueId { get { return _uniqueId; } }
        public override DateTimeOffset LastModified { get { return _lastModified; } }
        internal List<MemoryCacheEntry> Dependencies { get { return _dependencies; } }
 
        internal MemoryCacheEntryChangeMonitor(ReadOnlyCollection<string> keys, string regionName, MemoryCache cache)
        {
            Debug.Assert(keys != null && keys.Count > 0, "keys != null && keys.Count > 0");
            _keys = keys;
            _regionName = regionName;
            InitDisposableMembers(cache);
        }
 
        // invoked by a cache entry dependency when it is released from the cache
        internal void OnCacheEntryReleased()
        {
            OnChanged(null);
        }
    }
}