File: Pools\PoolFactory.cs
Web Access
Project: src\src\Shared\Shared.csproj (Shared)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Shared.Diagnostics;
 
#pragma warning disable CA1716
namespace Microsoft.Shared.Pools;
 
/// <summary>
/// A factory of object pools.
/// </summary>
/// <remarks>
/// This class makes it easy to create efficient object pools used to improve performance by reducing
/// strain on the garbage collector.
/// </remarks>
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
internal static class PoolFactory
{
    internal const int DefaultCapacity = 1024;
    private const int DefaultMaxStringBuilderCapacity = 64 * 1024;
    private const int InitialStringBuilderCapacity = 128;
 
    private static readonly IPooledObjectPolicy<StringBuilder> _defaultStringBuilderPolicy = new StringBuilderPooledObjectPolicy
    {
        InitialCapacity = InitialStringBuilderCapacity,
        MaximumRetainedCapacity = DefaultCapacity
    };
 
    /// <summary>
    /// Creates an object pool.
    /// </summary>
    /// <typeparam name="T">The type of object to keep in the pool.</typeparam>
    /// <param name="maxCapacity">The maximum number of items to keep in the pool. This defaults to 1024. This value is a recommendation, the pool may keep more objects than this.</param>
    /// <returns>The pool.</returns>
    public static ObjectPool<T> CreatePool<T>(int maxCapacity = DefaultCapacity)
        where T : class, new()
    {
        _ = Throw.IfLessThan(maxCapacity, 1);
 
        return MakePool(NoopPooledObjectPolicy<T>.Instance, maxCapacity);
    }
 
    /// <summary>
    /// Creates an object pool with a custom policy.
    /// </summary>
    /// <typeparam name="T">The type of object to keep in the pool.</typeparam>
    /// <param name="policy">The custom policy that is responsible for creating new objects and preparing objects to be added to the pool.</param>
    /// <param name="maxCapacity">The maximum number of items to keep in the pool. This defaults to 1024. This value is a recommendation, the pool may keep more objects than this.</param>
    /// <returns>The pool.</returns>
    public static ObjectPool<T> CreatePool<T>(IPooledObjectPolicy<T> policy, int maxCapacity = DefaultCapacity)
        where T : class
    {
        _ = Throw.IfNull(policy);
        _ = Throw.IfLessThan(maxCapacity, 1);
 
        return MakePool(policy, maxCapacity);
    }
 
    /// <summary>
    /// Creates an object pool for resettable objects.
    /// </summary>
    /// <typeparam name="T">The type of object to keep in the pool.</typeparam>
    /// <param name="maxCapacity">The maximum number of items to keep in the pool. This defaults to 1024. This value is a recommendation, the pool may keep more objects than this.</param>
    /// <returns>The pool.</returns>
    /// <remarks>
    /// Objects are systematically reset before being added to the pool.
    /// </remarks>
    public static ObjectPool<T> CreateResettingPool<T>(int maxCapacity = DefaultCapacity)
        where T : class, IResettable, new()
    {
        _ = Throw.IfLessThan(maxCapacity, 1);
 
        return MakePool(new DefaultPooledObjectPolicy<T>(), maxCapacity);
    }
 
    /// <summary>
    /// Creates a pool of <see cref="StringBuilder"/> instances.
    /// </summary>
    /// <param name="maxCapacity">The maximum number of items to keep in the pool. This defaults to 1024. This value is a recommendation, the pool may keep more objects than this.</param>
    /// <param name="maxStringBuilderCapacity">The maximum capacity of the string builders to keep in the pool. This defaults to 64K.</param>
    /// <returns>The pool.</returns>
    public static ObjectPool<StringBuilder> CreateStringBuilderPool(int maxCapacity = DefaultCapacity, int maxStringBuilderCapacity = DefaultMaxStringBuilderCapacity)
    {
        _ = Throw.IfLessThan(maxCapacity, 1);
        _ = Throw.IfLessThan(maxStringBuilderCapacity, 1);
 
        if (maxStringBuilderCapacity == DefaultMaxStringBuilderCapacity)
        {
            return MakePool(_defaultStringBuilderPolicy, maxCapacity);
        }
 
        return MakePool(
            new StringBuilderPooledObjectPolicy
            {
                InitialCapacity = InitialStringBuilderCapacity,
                MaximumRetainedCapacity = maxStringBuilderCapacity
            }, maxCapacity);
    }
 
    /// <summary>
    /// Creates an object pool of <see cref="List{T}"/> instances.
    /// </summary>
    /// <typeparam name="T">The type of object held by the lists.</typeparam>
    /// <param name="maxCapacity">
    /// The maximum number of items to keep in the pool.
    /// This defaults to 1024.
    /// This value is a recommendation, the pool may keep more objects than this.
    /// </param>
    /// <returns>The pool.</returns>
    public static ObjectPool<List<T>> CreateListPool<T>(int maxCapacity = DefaultCapacity)
    {
        _ = Throw.IfLessThan(maxCapacity, 1);
 
        return MakePool(PooledListPolicy<T>.Instance, maxCapacity);
    }
 
    /// <summary>
    /// Creates an object pool of <see cref="Dictionary{TKey, TValue}"/> instances.
    /// </summary>
    /// <typeparam name="TKey">The type of the dictionary keys.</typeparam>
    /// <typeparam name="TValue">The type of the dictionary values.</typeparam>
    /// <param name="comparer">Optional key comparer used by the dictionaries.</param>
    /// <param name="maxCapacity">
    /// The maximum number of items to keep in the pool.
    /// This defaults to 1024.
    /// This value is a recommendation, the pool may keep more objects than this.
    /// </param>
    /// <returns>The pool.</returns>
    public static ObjectPool<Dictionary<TKey, TValue>> CreateDictionaryPool<TKey, TValue>(IEqualityComparer<TKey>? comparer = null, int maxCapacity = DefaultCapacity)
        where TKey : notnull
    {
        _ = Throw.IfLessThan(maxCapacity, 1);
 
        return MakePool(new PooledDictionaryPolicy<TKey, TValue>(comparer), maxCapacity);
    }
 
    /// <summary>
    /// Creates an object pool of <see cref="HashSet{T}"/> instances.
    /// </summary>
    /// <typeparam name="T">The type of objects held in the sets.</typeparam>
    /// <param name="comparer">Optional key comparer used by the sets.</param>
    /// <param name="maxCapacity">
    /// The maximum number of items to keep in the pool.
    /// This defaults to 1024.
    /// This value is a recommendation, the pool may keep more objects than this.
    /// </param>
    /// <returns>The pool.</returns>
    public static ObjectPool<HashSet<T>> CreateHashSetPool<T>(IEqualityComparer<T>? comparer = null, int maxCapacity = DefaultCapacity)
        where T : notnull
    {
        _ = Throw.IfLessThan(maxCapacity, 1);
 
        return MakePool(new PooledSetPolicy<T>(comparer), maxCapacity);
    }
 
    /// <summary>
    /// Creates an object pool of <see cref="CancellationTokenSource" /> instances.
    /// </summary>
    /// <param name="maxCapacity">
    /// The maximum number of items to keep in the pool.
    /// This defaults to 1024.
    /// This value is a recommendation, the pool may keep more objects than this.
    /// </param>
    /// <returns>The pool.</returns>
    /// <remarks>
    /// On .NET 6 and above, cancellation token sources are reusable and this pool leverages this feature.
    /// When running on older frameworks, this pool is actually a no-op, every time a source is fetched
    /// from the pool, it is always a new instance. In that case, returning an object to the pool merely
    /// disposes it.
    /// </remarks>
    public static ObjectPool<CancellationTokenSource> CreateCancellationTokenSourcePool(int maxCapacity = DefaultCapacity)
    {
        _ = Throw.IfLessThan(maxCapacity, 1);
 
        return MakePool(PooledCancellationTokenSourcePolicy.Instance, maxCapacity);
    }
 
    /// <summary>
    /// Gets the shared pool of <see cref="StringBuilder"/> instances.
    /// </summary>
    public static ObjectPool<StringBuilder> SharedStringBuilderPool { get; } = CreateStringBuilderPool();
 
    private static DefaultObjectPool<T> MakePool<T>(IPooledObjectPolicy<T> policy, int maxRetained)
        where T : class
        => new(policy, maxRetained);
}