/// <summary>
        /// Creates a memoization cache factory for memoization caches that use an LRU cache eviction strategy.
        /// </summary>
        /// <param name="maxCapacity">The maximum capacity of memoization caches returned by the factory.</param>
        /// <returns>Memoization cache factory for memoization caches that use an LRU cache eviction strategy.</returns>
        public static IMemoizationCacheFactory CreateLru(int maxCapacity)
        {
            if (maxCapacity < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(maxCapacity), "A cache should have at a capacity of at least one.");
            }

            //
            // NB: For now, we use the Synchronized sledgehammer to achieve the concurrency safety. There may be
            //     cheaper ways by creating a custom implementation but we'll postpone this until later.
            //
            return(MemoizationCacheFactory.CreateLru(maxCapacity).Synchronized());
        }
        /// <summary>
        /// Creates a memoization cache factory for memoization caches that use an eviction strategy based on a function to rank cache entries based on metrics.
        /// Entries that meet the age threshold and have the highest score as computed by the ranker get evicted.
        /// </summary>
        /// <typeparam name="TMetric">Type of the metric to rank cache entries by.</typeparam>
        /// <param name="ranker">The ranker function used to obtain the metric for each entry upon evicting entries from the cache.</param>
        /// <param name="maxCapacity">The maximum capacity of memoization caches returned by the factory.</param>
        /// <param name="ageThreshold">The threshold used to decide whether an entry has aged sufficiently in order to be considered for eviction. E.g. a value of 0.9 means that the youngest 10% of entries cannot get evicted.</param>
        /// <param name="stopwatchFactory">The stopwatch factory used to create stopwatches that measure access times and function invocation times. If omitted, the default stopwatch is used.</param>
        /// <returns>Memoization cache factory for memoization caches that use a ranking-based cache eviction strategy.</returns>
        public static IMemoizationCacheFactory CreateEvictedByHighest <TMetric>(Func <IMemoizationCacheEntryMetrics, TMetric> ranker, int maxCapacity, double ageThreshold = 0.9, IStopwatchFactory stopwatchFactory = null)
        {
            if (ranker == null)
            {
                throw new ArgumentNullException(nameof(ranker));
            }
            if (maxCapacity < 1)
            {
                throw new ArgumentOutOfRangeException(nameof(maxCapacity), "A cache should have at a capacity of at least one.");
            }
            if (ageThreshold is <= 0 or > 1)
            {
                throw new ArgumentOutOfRangeException(nameof(ageThreshold), "The age threshold should be a number between 0 (inclusive) and 1 (exclusive).");
            }

            //
            // NB: For now, we use the Synchronized sledgehammer to achieve the concurrency safety. There may be
            //     cheaper ways by creating a custom implementation but we'll postpone this until later.
            //
            return(MemoizationCacheFactory.CreateEvictedByHighest(ranker, maxCapacity, ageThreshold, stopwatchFactory).Synchronized());
        }