File: src\RoslynAnalyzers\Utilities\FlowAnalysis\SingleThreadedConcurrentDictionary.cs
Web Access
Project: src\src\RoslynAnalyzers\Microsoft.CodeAnalysis.AnalyzerUtilities\Microsoft.CodeAnalysis.AnalyzerUtilities.csproj (Microsoft.CodeAnalysis.AnalyzerUtilities)
// 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;
using System.Collections.Concurrent;
 
namespace Analyzer.Utilities
{
    /// <summary>
    /// Like ConcurrentDictionary, but single threaded valueFactory execution in GetOrAdd.
    /// </summary>
    /// <remarks>Useful for long running valueFactory functions, like say performing 
    /// dataflow analysis.  This way DFA is invoked only once per key, even if multiple
    /// threads simultaneously request the same key.</remarks>
#pragma warning disable CA1812    // SingleThreadedConcurrentDictionary is too used.
    internal sealed class SingleThreadedConcurrentDictionary<TKey, TValue>
        where TValue : class
#pragma warning restore CA1812
    {
        /// <summary>
        /// An Entry itself serves a lock object, and contains the real value.
        /// </summary>
        private sealed class Entry
        {
            public TValue? Value { get; set; }
        }
 
        /// <summary>
        /// Holds entries, which contain the actual values.
        /// </summary>
        private readonly ConcurrentDictionary<TKey, Entry> BackingDictionary = new();
 
        /// <summary>
        /// Adds a key/value pair using the specified function if the key does not already exist.  Returns the new value, or the existing value if the key exists.
        /// </summary>
        /// <param name="key">Key to add.</param>
        /// <param name="valueFactory">Function to be invoked to generate the key, if necessary.</param>
        /// <returns>Value of the key, which will either be the existing value, or new value if the key was not in the dictionary.</returns>
        public TValue? GetOrAdd(TKey key, Func<TKey, TValue?> valueFactory)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
 
            if (valueFactory == null)
            {
                throw new ArgumentNullException(nameof(valueFactory));
            }
 
            // The return value of ConcurrentDictionary.GetOrAdd() is
            // consistent, i.e. the same instance no matter how many times
            // this valueFactory gets executed.  So for a given key, we'll
            // always get back the same Entry instance.
            Entry entry = this.BackingDictionary.GetOrAdd(key, (_) => new Entry());
            if (entry.Value != null)
            {
                return entry.Value;
            }
 
            lock (entry)
            {
                entry.Value ??= valueFactory(key);
            }
 
            return entry.Value;
        }
    }
}