File: OptionsCache.cs
Web Access
Project: src\src\libraries\Microsoft.Extensions.Options\src\Microsoft.Extensions.Options.csproj (Microsoft.Extensions.Options)
// 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.Concurrent;
using System.Diagnostics.CodeAnalysis;
 
namespace Microsoft.Extensions.Options
{
    /// <summary>
    /// Caches <typeparamref name="TOptions"/> instances.
    /// </summary>
    /// <typeparam name="TOptions">The type of options being requested.</typeparam>
    public class OptionsCache<[DynamicallyAccessedMembers(Options.DynamicallyAccessedMembers)] TOptions> :
        IOptionsMonitorCache<TOptions>
        where TOptions : class
    {
        private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(concurrencyLevel: 1, capacity: 31, StringComparer.Ordinal); // 31 == default capacity
 
        /// <summary>
        /// Clears all options instances from the cache.
        /// </summary>
        public void Clear() => _cache.Clear();
 
        /// <summary>
        /// Gets a named options instance, or adds a new instance created with <paramref name="createOptions"/>.
        /// </summary>
        /// <param name="name">The name of the options instance.</param>
        /// <param name="createOptions">The function used to create the new instance.</param>
        /// <returns>The options instance.</returns>
        public virtual TOptions GetOrAdd(string? name, Func<TOptions> createOptions)
        {
            ThrowHelper.ThrowIfNull(createOptions);
 
            name ??= Options.DefaultName;
            Lazy<TOptions> value;
 
#if NET || NETSTANDARD2_1
            value = _cache.GetOrAdd(name, static (name, createOptions) => new Lazy<TOptions>(createOptions), createOptions);
#else
            if (!_cache.TryGetValue(name, out value))
            {
                value = _cache.GetOrAdd(name, new Lazy<TOptions>(createOptions));
            }
#endif
 
            return value.Value;
        }
 
        internal TOptions GetOrAdd<TArg>(string? name, Func<string, TArg, TOptions> createOptions, TArg factoryArgument)
        {
            // For compatibility, fall back to public GetOrAdd() if we're in a derived class.
            // For simplicity, we do the same for older frameworks that don't support the factoryArgument overload of GetOrAdd().
#if NET || NETSTANDARD2_1
            if (GetType() != typeof(OptionsCache<TOptions>))
#endif
            {
                // copying captured variables to locals avoids allocating a closure if we don't enter the if
                string? localName = name;
                Func<string, TArg, TOptions> localCreateOptions = createOptions;
                TArg localFactoryArgument = factoryArgument;
                return GetOrAdd(name, () => localCreateOptions(localName ?? Options.DefaultName, localFactoryArgument));
            }
 
#if NET || NETSTANDARD2_1
            return _cache.GetOrAdd(
                name ?? Options.DefaultName,
                static (name, arg) => new Lazy<TOptions>(() => arg.createOptions(name, arg.factoryArgument)), (createOptions, factoryArgument)).Value;
#endif
        }
 
        /// <summary>
        /// Gets a named options instance, if available.
        /// </summary>
        /// <param name="name">The name of the options instance.</param>
        /// <param name="options">The options instance.</param>
        /// <returns><see langword="true"/> if the options were retrieved; otherwise, <see langword="false"/>.</returns>
        internal bool TryGetValue(string? name, [MaybeNullWhen(false)] out TOptions options)
        {
            if (_cache.TryGetValue(name ?? Options.DefaultName, out Lazy<TOptions>? lazy))
            {
                options = lazy.Value;
                return true;
            }
 
            options = default;
            return false;
        }
 
        /// <summary>
        /// Tries to adds a new option to the cache.
        /// </summary>
        /// <param name="name">The name of the options instance.</param>
        /// <param name="options">The options instance.</param>
        /// <returns><see langword="true"/> if the option was added; <see langword="false"/> if the name already exists.</returns>
        public virtual bool TryAdd(string? name, TOptions options)
        {
            ThrowHelper.ThrowIfNull(options);
 
            return _cache.TryAdd(name ?? Options.DefaultName, new Lazy<TOptions>(
#if !(NET || NETSTANDARD2_1)
                () =>
#endif
                options));
        }
 
        /// <summary>
        /// Tries to remove an options instance.
        /// </summary>
        /// <param name="name">The name of the options instance.</param>
        /// <returns><see langword="true"/> if anything was removed; otherwise, <see langword="false"/>.</returns>
        public virtual bool TryRemove(string? name) =>
            _cache.TryRemove(name ?? Options.DefaultName, out _);
    }
}