| File: OptionsCache.cs | Web Access |
| Project: src\runtime\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) { ArgumentNullException.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) { ArgumentNullException.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 _); } }