| File: src\Shared\Memoization\MemoizedFunction.cs | Web Access |
| Project: src\src\Libraries\Microsoft.Extensions.Telemetry\Microsoft.Extensions.Telemetry.csproj (Microsoft.Extensions.Telemetry) |
// 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.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Shared.Diagnostics; #pragma warning disable CA1716 namespace Microsoft.Shared.Memoization; #pragma warning restore CA1716 #pragma warning disable SA1402 // File may only contain a single type /// <summary> /// Memoizer for functions of arity 1. /// </summary> /// <remarks> /// We don't use weak references because those can only wrap reference types, and we wish to support functions that return other kinds of values. /// </remarks> /// <typeparam name="TParameter">Input parameter type for the memoized function.</typeparam> /// <typeparam name="TResult">Return type for the memoized function.</typeparam> #if !SHARED_PROJECT [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] #endif [DebuggerDisplay("{_values.Count} memoized values")] internal sealed class MemoizedFunction<TParameter, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult> { private const int Concurrency = 10; private const int Capacity = 100; // Using a readonly struct means that we can assert in the ConcurrentDictionary // declaration that all keys are non-null. Otherwise we need to say so in the // type declaration ("where TParameter : notnull"), which forces users to care. internal readonly struct Arg : IEquatable<MemoizedFunction<TParameter, TResult>.Arg> { private readonly int _hash; public Arg(TParameter arg1) { Arg1 = arg1; _hash = Arg1?.GetHashCode() ?? 0; } public readonly TParameter Arg1; public override bool Equals(object? obj) => obj is MemoizedFunction<TParameter, TResult>.Arg arg && Equals(arg); public bool Equals(MemoizedFunction<TParameter, TResult>.Arg other) => EqualityComparer<TParameter>.Default.Equals(Arg1, other.Arg1); public override int GetHashCode() => _hash; } private readonly ConcurrentDictionary<Arg, Lazy<TResult>> _values; private readonly Func<TParameter, TResult> _function; /// <summary> /// Initializes a new instance of the <see cref="MemoizedFunction{TParameter1, TResult}"/> class. /// </summary> /// <param name="function">The function whose results will be memoized.</param> public MemoizedFunction(Func<TParameter, TResult> function) { _function = Throw.IfNull(function); _values = new(Concurrency, Capacity); } internal TResult Function(TParameter arg1) { var arg = new Arg(arg1); // Stryker disable once all if (_values.TryGetValue(arg, out var result)) { return result.Value; } return _values.GetOrAdd(arg, new Lazy<TResult>(() => _function(arg1))).Value; } } /// <summary> /// Memoizer for functions of arity 2. /// </summary> /// <typeparam name="TParameter1">First input parameter type for the memoized function.</typeparam> /// <typeparam name="TParameter2">Second input parameter type for the memoized function.</typeparam> /// <typeparam name="TResult">Return type for the memoized function.</typeparam> #if !SHARED_PROJECT [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] #endif [SuppressMessage( "Major Code Smell", "S2436:Types and methods should not have too many generic parameters", Justification = "We're using many generic types for the same reason Func<>, Func<,>, Func<,,>, ... exist.")] [DebuggerDisplay("{_values.Count} memoized values")] internal sealed class MemoizedFunction<TParameter1, TParameter2, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult> { private const int Concurrency = 10; private const int Capacity = 100; internal readonly struct Args : IEquatable<MemoizedFunction<TParameter1, TParameter2, TResult>.Args> { private readonly int _hash; public Args(TParameter1 arg1, TParameter2 arg2) { Arg1 = arg1; Arg2 = arg2; _hash = HashCode.Combine(Arg1, Arg2); } public readonly TParameter1 Arg1; public readonly TParameter2 Arg2; public override bool Equals(object? obj) => obj is MemoizedFunction<TParameter1, TParameter2, TResult>.Args args && Equals(args); public bool Equals(MemoizedFunction<TParameter1, TParameter2, TResult>.Args other) => EqualityComparer<TParameter1>.Default.Equals(Arg1, other.Arg1) && EqualityComparer<TParameter2>.Default.Equals(Arg2, other.Arg2); public override int GetHashCode() => _hash; } private readonly ConcurrentDictionary<Args, Lazy<TResult>> _values; private readonly Func<TParameter1, TParameter2, TResult> _function; /// <summary> /// Initializes a new instance of the <see cref="MemoizedFunction{TParameter1, TParameter2, TResult}"/> class. /// </summary> /// <param name="function">The function whose results will be memoized.</param> public MemoizedFunction(Func<TParameter1, TParameter2, TResult> function) { _function = Throw.IfNull(function); _values = new(Concurrency, Capacity); } internal TResult Function(TParameter1 arg1, TParameter2 arg2) { var args = new Args(arg1, arg2); // Stryker disable once all if (_values.TryGetValue(args, out var result)) { return result.Value; } return _values.GetOrAdd(args, new Lazy<TResult>(() => _function(arg1, arg2))).Value; } }