Repository: dotnet/runtime
File: src/libraries/Microsoft.Extensions.Caching.Memory/src/MemoryCache.cs (C#)

Code before:
        }


        internal sealed class Stats
        {
            private readonly MemoryCache _memoryCache;
            public long Hits;
            public long Misses;

            public Stats(MemoryCache memoryCache)
            {
                _memoryCache = memoryCache;
                _memoryCache.AddToStats(this);
            }

            internal void RemoveFromStats() => _memoryCache.RemoveFromStats(this);
        }

        private void RemoveFromStats(Stats current)
        {
            lock (_allStats!)
            {
                _accumulatedHits += Volatile.Read(ref current.Hits);
                _accumulatedMisses += Volatile.Read(ref current.Misses);

                _allStats.Remove(current);
                _allStats.TrimExcess();
            }
        }

        private void AddToStats(Stats current)
        {
            lock (_allStats!)
            {
                _allStats.Add(current);
            }
        }

        private void ScanForExpiredItems()
        {
            DateTime utcNow = _lastExpirationScan = UtcNow;

            CoherentState coherentState = _coherentState; // Clear() can update the reference in the meantime

            foreach (CacheEntry entry in coherentState.GetAllValues())
            {
                if (entry.CheckExpired(utcNow))
                {
                    coherentState.RemoveEntry(entry, _options);
                }
            }

Review comment (performance):
@rhuijben, I noticed your concern about potential memory leaks. In this change we’re switching from a broad sweep to removing specific items, but finalizers should still run normally. The only case where they wouldn’t is if the process terminates between GC and finalization — and in that scenario, the process is ending anyway, so there’s no lasting memory‑leak impact because all memory is released back to the OS.
Am I missing something here, or are we safe with this approach?

Code after:
        }


        internal sealed class Stats
        {
            private readonly MemoryCache _memoryCache;
            public long Hits;
            public long Misses;
            public int Index;

            public Stats(MemoryCache memoryCache)
            {
                _memoryCache = memoryCache;
                _memoryCache.AddToStats(this);
            }

            internal void RemoveFromStats() => _memoryCache.RemoveFromStats(this);
        }

        private void RemoveFromStats(Stats current)
        {
            lock (_allStats!)
            {
                _accumulatedHits += Volatile.Read(ref current.Hits);
                _accumulatedMisses += Volatile.Read(ref current.Misses);
                int indexToOverwrite = current.Index;
                int lastIndex = _allStats.Count - 1;

                if (indexToOverwrite < lastIndex)
                {
                    _allStats[indexToOverwrite] = _allStats[lastIndex];
                    _allStats[indexToOverwrite].Index = indexToOverwrite;
                }

                _allStats.RemoveAt(lastIndex);
                _allStats.TrimExcess();
            }
        }

        private void AddToStats(Stats current)
        {
            lock (_allStats!)
            {
                current.Index = _allStats.Count;
                _allStats.Add(current);
            }
        }

        private void ScanForExpiredItems()
        {
            DateTime utcNow = _lastExpirationScan = UtcNow;