File: Runtime\HybridCache.cs
Web Access
Project: src\src\Caching\Hybrid\src\Microsoft.Extensions.Caching.Hybrid.csproj (Microsoft.Extensions.Caching.Hybrid)
// 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;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
 
namespace Microsoft.Extensions.Caching.Hybrid;
 
/// <summary>
/// Provides multi-tier caching services building on <see cref="IDistributedCache"/> backends.
/// </summary>
public abstract class HybridCache
{
    /// <summary>
    /// Asynchronously gets the value associated with the key if it exists, or generates a new entry using the provided key and a value from the given factory if the key is not found.
    /// </summary>
    /// <typeparam name="T">The type of the data being considered.</typeparam>
    /// <typeparam name="TState">The type of additional state required by <paramref name="factory"/>.</typeparam>
    /// <param name="key">The key of the entry to look for or create.</param>
    /// <param name="factory">Provides the underlying data service is the data is not available in the cache.</param>
    /// <param name="state">Additional state required for <paramref name="factory"/>.</param>
    /// <param name="options">Additional options for this cache entry.</param>
    /// <param name="tags">The tags to associate with this cache item.</param>
    /// <param name="token">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
    /// <returns>The data, either from cache or the underlying data service.</returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")]
    public abstract ValueTask<T> GetOrCreateAsync<TState, T>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> factory,
        HybridCacheEntryOptions? options = null, IReadOnlyCollection<string>? tags = null, CancellationToken token = default);
 
    /// <summary>
    /// Asynchronously gets the value associated with the key if it exists, or generates a new entry using the provided key and a value from the given factory if the key is not found.
    /// </summary>
    /// <typeparam name="T">The type of the data being considered.</typeparam>
    /// <param name="key">The key of the entry to look for or create.</param>
    /// <param name="factory">Provides the underlying data service is the data is not available in the cache.</param>
    /// <param name="options">Additional options for this cache entry.</param>
    /// <param name="tags">The tags to associate with this cache item.</param>
    /// <param name="token">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
    /// <returns>The data, either from cache or the underlying data service.</returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Delegate differences make this unambiguous")]
    public ValueTask<T> GetOrCreateAsync<T>(string key, Func<CancellationToken, ValueTask<T>> factory,
        HybridCacheEntryOptions? options = null, IReadOnlyCollection<string>? tags = null, CancellationToken token = default)
        => GetOrCreateAsync(key, factory, WrappedCallbackCache<T>.Instance, options, tags, token);
 
    private static class WrappedCallbackCache<T> // per-T memoized helper that allows GetOrCreateAsync<T> and GetOrCreateAsync<TState, T> to share an implementation
    {
        // for the simple usage scenario (no TState), pack the original callback as the "state", and use a wrapper function that just unrolls and invokes from the state
        public static readonly Func<Func<CancellationToken, ValueTask<T>>, CancellationToken, ValueTask<T>> Instance = static (callback, ct) => callback(ct);
    }
 
    /// <summary>
    /// Asynchronously sets or overwrites the value associated with the key.
    /// </summary>
    /// <typeparam name="T">The type of the data being considered.</typeparam>
    /// <param name="key">The key of the entry to create.</param>
    /// <param name="value">The value to assign for this cache entry.</param>
    /// <param name="options">Additional options for this cache entry.</param>
    /// <param name="tags">The tags to associate with this cache entry.</param>
    /// <param name="token">The <see cref="CancellationToken"/> used to propagate notifications that the operation should be canceled.</param>
    public abstract ValueTask SetAsync<T>(string key, T value, HybridCacheEntryOptions? options = null, IReadOnlyCollection<string>? tags = null, CancellationToken token = default);
 
    /// <summary>
    /// Asynchronously removes the value associated with the key if it exists.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous in context")]
    public abstract ValueTask RemoveAsync(string key, CancellationToken token = default);
 
    /// <summary>
    /// Asynchronously removes the value associated with the key if it exists.
    /// </summary>
    /// <remarks>Implementors should treat <c>null</c> as empty</remarks>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous in context")]
    public virtual ValueTask RemoveAsync(IEnumerable<string> keys, CancellationToken token = default)
    {
        return keys switch
        {
            // for consistency with GetOrCreate/Set: interpret null as "none"
            null or ICollection<string> { Count: 0 } => default,
            ICollection<string> { Count: 1 } => RemoveByTagAsync(keys.Single(), token),
            _ => ForEachAsync(this, keys, token),
        };
 
        // default implementation is to call RemoveKeyAsync for each key in turn
        static async ValueTask ForEachAsync(HybridCache @this, IEnumerable<string> keys, CancellationToken token)
        {
            foreach (var key in keys)
            {
                await @this.RemoveAsync(key, token).ConfigureAwait(false);
            }
        }
    }
 
    /// <summary>
    /// Asynchronously removes all values associated with the specified tags.
    /// </summary>
    /// <remarks>Implementors should treat <c>null</c> as empty</remarks>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous in context")]
    public virtual ValueTask RemoveByTagAsync(IEnumerable<string> tags, CancellationToken token = default)
    {
        return tags switch
        {
            // for consistency with GetOrCreate/Set: interpret null as "none"
            null or ICollection<string> { Count: 0 } => default,
            ICollection<string> { Count: 1 } => RemoveByTagAsync(tags.Single(), token),
            _ => ForEachAsync(this, tags, token),
        };
 
        // default implementation is to call RemoveTagAsync for each key in turn
        static async ValueTask ForEachAsync(HybridCache @this, IEnumerable<string> keys, CancellationToken token)
        {
            foreach (var key in keys)
            {
                await @this.RemoveByTagAsync(key, token).ConfigureAwait(false);
            }
        }
    }
 
    /// <summary>
    /// Asynchronously removes all values associated with the specified tag.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Not ambiguous in context")]
    public abstract ValueTask RemoveByTagAsync(string tag, CancellationToken token = default);
}