Guest User

Untitled

a guest
Jul 9th, 2025
19
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 13.35 KB | None | 0 0
  1. using BenchmarkDotNet.Attributes;
  2. using Microsoft.Extensions.Options;
  3. using System;
  4. using System.Collections.Concurrent;
  5. using System.Diagnostics.CodeAnalysis;
  6. using System.Runtime.Intrinsics.Arm;
  7. using System.Security.Cryptography;
  8. using System.Threading;
  9. using static System.Runtime.InteropServices.JavaScript.JSType;
  10.  
  11. namespace OptionsMonitor
  12. {
  13.     // For more information on the VS BenchmarkDotNet Diagnosers see https://learn.microsoft.com/visualstudio/profiling/profiling-with-benchmark-dotnet
  14.     [MemoryDiagnoser]
  15.     public class Benchmarks
  16.     {
  17.         private OptionsCache<MyOptions> optionsCache;
  18.         private OptionsCacheBetter<MyOptions> optionsCacheBetter;
  19.  
  20.         [GlobalSetup]
  21.         public void Setup()
  22.         {
  23.             optionsCache = new OptionsCache<MyOptions>();
  24.             optionsCache.TryAdd(null, new MyOptions { Name = "Default", Value = 42 });
  25.  
  26.             optionsCacheBetter = new OptionsCacheBetter<MyOptions>();
  27.             optionsCacheBetter.TryAdd(null, new MyOptions { Name = "Default", Value = 42 });
  28.         }
  29.  
  30.         [Benchmark]
  31.         public string Baseline()
  32.         {
  33.             var opt = optionsCache.GetOrAdd(null, () => new MyOptions { Name = "Default", Value = 42 });
  34.             return opt.Name;
  35.         }
  36.  
  37.         [Benchmark]
  38.         public string Better()
  39.         {
  40.             var opt = optionsCacheBetter.GetOrAdd(null, () => new MyOptions { Name = "Default", Value = 42 });
  41.             return opt.Name;
  42.         }
  43.  
  44.     }
  45.  
  46.     class MyOptions
  47.     {
  48.         public string? Name { get; set; }
  49.         public int Value { get; set; }
  50.     }
  51.  
  52.     public class OptionsCacheBetter<TOptions> :
  53.     IOptionsMonitorCache<TOptions>
  54.     where TOptions : class
  55.     {
  56.         private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(concurrencyLevel: 1, capacity: 31, StringComparer.Ordinal); // 31 == default capacity
  57.         private Lazy<TOptions>? _defaultOptions = null;
  58.  
  59.         /// <summary>
  60.         /// Clears all options instances from the cache.
  61.         /// </summary>
  62.         public void Clear() => _cache.Clear();
  63.  
  64.         /// <summary>
  65.         /// Gets a named options instance, or adds a new instance created with <paramref name="createOptions"/>.
  66.         /// </summary>
  67.         /// <param name="name">The name of the options instance.</param>
  68.         /// <param name="createOptions">The function used to create the new instance.</param>
  69.         /// <returns>The options instance.</returns>
  70.         public virtual TOptions GetOrAdd(string? name, Func<TOptions> createOptions)
  71.         {
  72.             ArgumentNullException.ThrowIfNull(createOptions);
  73.  
  74.             name ??= Options.DefaultName;
  75.             Lazy<TOptions> value;
  76.  
  77.             if (name == Options.DefaultName)
  78.             {
  79.                 if (_defaultOptions is null)
  80.                 {
  81.                     // We need a reference to the new instance to be able to return it. Usage of `return _defaultOptions.Value`
  82.                     // could technically save us some allocations but it would have a risk of sneaky race condition of .Clear
  83.                     // being called between the Interlocked.CompareExchange call assigning new value and the return, leading to NRE.
  84.                     var newDefaultOptions = new Lazy<TOptions>(createOptions);
  85.                     var result = Interlocked.CompareExchange(ref _defaultOptions, newDefaultOptions, null);
  86.  
  87.                     return result is not null ? result.Value : newDefaultOptions.Value;
  88.                 }
  89.                 return _defaultOptions.Value;
  90.             }
  91.  
  92. #if NET || NETSTANDARD2_1
  93.             value = _cache.GetOrAdd(name, static (name, createOptions) => new Lazy<TOptions>(createOptions), createOptions);
  94. #else
  95.             if (!_cache.TryGetValue(name, out value))
  96.             {
  97.                 value = _cache.GetOrAdd(name, new Lazy<TOptions>(createOptions));
  98.             }
  99. #endif
  100.  
  101.             return value.Value;
  102.         }
  103.  
  104.         internal TOptions GetOrAdd<TArg>(string? name, Func<string, TArg, TOptions> createOptions, TArg factoryArgument)
  105.         {
  106.             // For compatibility, fall back to public GetOrAdd() if we're in a derived class.
  107.             // For simplicity, we do the same for older frameworks that don't support the factoryArgument overload of GetOrAdd().
  108.  
  109.             if (name == Options.DefaultName)
  110.             {
  111.                 if (_defaultOptions is null)
  112.                 {
  113.                     // We need a reference to the new instance to be able to return it. Usage of `return _defaultOptions.Value`
  114.                     // could technically save us some allocations but it would have a risk of sneaky race condition of .Clear
  115.                     // being called between the Interlocked.CompareExchange call assigning new value and the return, leading to NRE.
  116.                     var newDefaultOptions = new Lazy<TOptions>(() => createOptions(Options.DefaultName, factoryArgument));
  117.                     var result = Interlocked.CompareExchange(ref _defaultOptions, newDefaultOptions, null);
  118.  
  119.                     return result is not null ? result.Value : newDefaultOptions.Value;
  120.                 }
  121.                 return _defaultOptions.Value;
  122.             }
  123.  
  124. #if NET || NETSTANDARD2_1
  125.             if (GetType() != typeof(OptionsCache<TOptions>))
  126. #endif
  127.             {
  128.                 // copying captured variables to locals avoids allocating a closure if we don't enter the if
  129.                 string? localName = name;
  130.                 Func<string, TArg, TOptions> localCreateOptions = createOptions;
  131.                 TArg localFactoryArgument = factoryArgument;
  132.                 return GetOrAdd(name, () => localCreateOptions(localName ?? Options.DefaultName, localFactoryArgument));
  133.             }
  134.  
  135. #if NET || NETSTANDARD2_1
  136.             return _cache.GetOrAdd(
  137.                 name ?? Options.DefaultName,
  138.                 static (name, arg) => new Lazy<TOptions>(() => arg.createOptions(name, arg.factoryArgument)), (createOptions, factoryArgument)).Value;
  139. #endif
  140.         }
  141.  
  142.         /// <summary>
  143.         /// Gets a named options instance, if available.
  144.         /// </summary>
  145.         /// <param name="name">The name of the options instance.</param>
  146.         /// <param name="options">The options instance.</param>
  147.         /// <returns><see langword="true"/> if the options were retrieved; otherwise, <see langword="false"/>.</returns>
  148.         internal bool TryGetValue(string? name, [MaybeNullWhen(false)] out TOptions options)
  149.         {
  150.             if (name == Options.DefaultName)
  151.             {
  152.                 if (_defaultOptions is { } defaultOptions)
  153.                 {
  154.                     options = defaultOptions.Value;
  155.                     return true;
  156.                 }
  157.                 options = default;
  158.                 return false;
  159.             }
  160.  
  161.             if (_cache.TryGetValue(name ?? Options.DefaultName, out Lazy<TOptions>? lazy))
  162.             {
  163.                 options = lazy.Value;
  164.                 return true;
  165.             }
  166.  
  167.             options = default;
  168.             return false;
  169.         }
  170.  
  171.         /// <summary>
  172.         /// Tries to adds a new option to the cache.
  173.         /// </summary>
  174.         /// <param name="name">The name of the options instance.</param>
  175.         /// <param name="options">The options instance.</param>
  176.         /// <returns><see langword="true"/> if the option was added; <see langword="false"/> if the name already exists.</returns>
  177.         public virtual bool TryAdd(string? name, TOptions options)
  178.         {
  179.             ArgumentNullException.ThrowIfNull(options);
  180.  
  181.             if (name == Options.DefaultName)
  182.             {
  183.                 if (_defaultOptions is not null)
  184.                 {
  185.                     return false; // Default options already exist
  186.                 }
  187.                 var result = Interlocked.CompareExchange(ref _defaultOptions, new Lazy<TOptions>(() => options), null);
  188.                 return result is null;
  189.             }
  190.  
  191.             return _cache.TryAdd(name ?? Options.DefaultName, new Lazy<TOptions>(
  192. #if !(NET || NETSTANDARD2_1)
  193.                 () =>
  194. #endif
  195.                 options));
  196.         }
  197.  
  198.         /// <summary>
  199.         /// Tries to remove an options instance.
  200.         /// </summary>
  201.         /// <param name="name">The name of the options instance.</param>
  202.         /// <returns><see langword="true"/> if anything was removed; otherwise, <see langword="false"/>.</returns>
  203.         public virtual bool TryRemove(string? name)
  204.         {
  205.             if (name == Options.DefaultName)
  206.             {
  207.                 if (_defaultOptions is not null)
  208.                 {
  209.                     var result = Interlocked.Exchange(ref _defaultOptions, null);
  210.                     return result is not null;
  211.                 }
  212.                 return false;
  213.             }
  214.  
  215.             return _cache.TryRemove(name ?? Options.DefaultName, out _);
  216.         }
  217.     }
  218.  
  219.  
  220.     public class OptionsCache<TOptions> :
  221.     IOptionsMonitorCache<TOptions>
  222.     where TOptions : class
  223.     {
  224.         private readonly ConcurrentDictionary<string, Lazy<TOptions>> _cache = new ConcurrentDictionary<string, Lazy<TOptions>>(concurrencyLevel: 1, capacity: 31, StringComparer.Ordinal); // 31 == default capacity
  225.  
  226.         /// <summary>
  227.         /// Clears all options instances from the cache.
  228.         /// </summary>
  229.         public void Clear() => _cache.Clear();
  230.  
  231.         /// <summary>
  232.         /// Gets a named options instance, or adds a new instance created with <paramref name="createOptions"/>.
  233.         /// </summary>
  234.         /// <param name="name">The name of the options instance.</param>
  235.         /// <param name="createOptions">The function used to create the new instance.</param>
  236.         /// <returns>The options instance.</returns>
  237.         public virtual TOptions GetOrAdd(string? name, Func<TOptions> createOptions)
  238.         {
  239.             ArgumentNullException.ThrowIfNull(createOptions);
  240.  
  241.             name ??= Options.DefaultName;
  242.             Lazy<TOptions> value;
  243.  
  244. #if NET || NETSTANDARD2_1
  245.             value = _cache.GetOrAdd(name, static (name, createOptions) => new Lazy<TOptions>(createOptions), createOptions);
  246. #else
  247.             if (!_cache.TryGetValue(name, out value))
  248.             {
  249.                 value = _cache.GetOrAdd(name, new Lazy<TOptions>(createOptions));
  250.             }
  251. #endif
  252.  
  253.             return value.Value;
  254.         }
  255.  
  256.         internal TOptions GetOrAdd<TArg>(string? name, Func<string, TArg, TOptions> createOptions, TArg factoryArgument)
  257.         {
  258.             // For compatibility, fall back to public GetOrAdd() if we're in a derived class.
  259.             // For simplicity, we do the same for older frameworks that don't support the factoryArgument overload of GetOrAdd().
  260. #if NET || NETSTANDARD2_1
  261.             if (GetType() != typeof(OptionsCache<TOptions>))
  262. #endif
  263.             {
  264.                 // copying captured variables to locals avoids allocating a closure if we don't enter the if
  265.                 string? localName = name;
  266.                 Func<string, TArg, TOptions> localCreateOptions = createOptions;
  267.                 TArg localFactoryArgument = factoryArgument;
  268.                 return GetOrAdd(name, () => localCreateOptions(localName ?? Options.DefaultName, localFactoryArgument));
  269.             }
  270.  
  271. #if NET || NETSTANDARD2_1
  272.             return _cache.GetOrAdd(
  273.                 name ?? Options.DefaultName,
  274.                 static (name, arg) => new Lazy<TOptions>(() => arg.createOptions(name, arg.factoryArgument)), (createOptions, factoryArgument)).Value;
  275. #endif
  276.         }
  277.  
  278.         /// <summary>
  279.         /// Gets a named options instance, if available.
  280.         /// </summary>
  281.         /// <param name="name">The name of the options instance.</param>
  282.         /// <param name="options">The options instance.</param>
  283.         /// <returns><see langword="true"/> if the options were retrieved; otherwise, <see langword="false"/>.</returns>
  284.         internal bool TryGetValue(string? name, [MaybeNullWhen(false)] out TOptions options)
  285.         {
  286.             if (_cache.TryGetValue(name ?? Options.DefaultName, out Lazy<TOptions>? lazy))
  287.             {
  288.                 options = lazy.Value;
  289.                 return true;
  290.             }
  291.  
  292.             options = default;
  293.             return false;
  294.         }
  295.  
  296.         /// <summary>
  297.         /// Tries to adds a new option to the cache.
  298.         /// </summary>
  299.         /// <param name="name">The name of the options instance.</param>
  300.         /// <param name="options">The options instance.</param>
  301.         /// <returns><see langword="true"/> if the option was added; <see langword="false"/> if the name already exists.</returns>
  302.         public virtual bool TryAdd(string? name, TOptions options)
  303.         {
  304.             ArgumentNullException.ThrowIfNull(options);
  305.  
  306.             return _cache.TryAdd(name ?? Options.DefaultName, new Lazy<TOptions>(
  307. #if !(NET || NETSTANDARD2_1)
  308.                 () =>
  309. #endif
  310.                 options));
  311.         }
  312.  
  313.         /// <summary>
  314.         /// Tries to remove an options instance.
  315.         /// </summary>
  316.         /// <param name="name">The name of the options instance.</param>
  317.         /// <returns><see langword="true"/> if anything was removed; otherwise, <see langword="false"/>.</returns>
  318.         public virtual bool TryRemove(string? name) =>
  319.             _cache.TryRemove(name ?? Options.DefaultName, out _);
  320.     }
  321.  
  322. }
  323.  
Advertisement
Add Comment
Please, Sign In to add comment