File: Collections\ConcurrentCache.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
 
namespace Microsoft.CodeAnalysis
{
    // very simple cache with a specified size.
    // expiration policy is "new entry wins over old entry if hashed into the same bucket"
    internal sealed class ConcurrentCache<TKey, TValue> : CachingBase<ConcurrentCache<TKey, TValue>.Entry>
        where TKey : notnull
    {
        private readonly IEqualityComparer<TKey> _keyComparer;
 
        // class, to ensure atomic updates.
        internal class Entry
        {
            internal readonly int hash;
            internal readonly TKey key;
            internal readonly TValue value;
 
            internal Entry(int hash, TKey key, TValue value)
            {
                this.hash = hash;
                this.key = key;
                this.value = value;
            }
        }
 
        public ConcurrentCache(int size, IEqualityComparer<TKey> keyComparer)
            // Defer creating the backing array until it is actually needed.  This saves on expensive allocations for
            // short-lived compilations that do not end up using the cache.  As the cache is simple best-effort, it's
            // fine if multiple threads end up creating the backing array at the same time.  One thread will be last and
            // will win, and the others will just end up creating a small piece of garbage that will be collected.
            : base(size, createBackingArray: false)
        {
            _keyComparer = keyComparer;
        }
 
        public ConcurrentCache(int size)
            : this(size, EqualityComparer<TKey>.Default) { }
 
        public bool TryAdd(TKey key, TValue value)
        {
            var hash = _keyComparer.GetHashCode(key);
            var idx = hash & mask;
 
            var entry = this.Entries[idx];
            if (entry != null && entry.hash == hash && _keyComparer.Equals(entry.key, key))
            {
                return false;
            }
 
            Entries[idx] = new Entry(hash, key, value);
            return true;
        }
 
        public bool TryGetValue(TKey key, [MaybeNullWhen(returnValue: false)] out TValue value)
        {
            int hash = _keyComparer.GetHashCode(key);
            int idx = hash & mask;
 
            var entry = this.Entries[idx];
            if (entry != null && entry.hash == hash && _keyComparer.Equals(entry.key, key))
            {
                value = entry.value;
                return true;
            }
 
            value = default!;
            return false;
        }
    }
}