diff --git a/src/libraries/Microsoft.Extensions.Caching.Abstractions/ref/Microsoft.Extensions.Caching.Abstractions.cs b/src/libraries/Microsoft.Extensions.Caching.Abstractions/ref/Microsoft.Extensions.Caching.Abstractions.cs index 11479bb17ef608..2545c9d147aba9 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Abstractions/ref/Microsoft.Extensions.Caching.Abstractions.cs +++ b/src/libraries/Microsoft.Extensions.Caching.Abstractions/ref/Microsoft.Extensions.Caching.Abstractions.cs @@ -193,7 +193,7 @@ public enum HybridCacheEntryFlags DisableUnderlyingData = 1 << 4, DisableCompression = 1 << 5, } - public abstract class HybridCache + public abstract partial class HybridCache { public abstract System.Threading.Tasks.ValueTask GetOrCreateAsync(string key, TState state, System.Func> factory, HybridCacheEntryOptions? options = null, System.Collections.Generic.IEnumerable? tags = null, System.Threading.CancellationToken cancellationToken = default); diff --git a/src/libraries/Microsoft.Extensions.Caching.Abstractions/ref/Microsoft.Extensions.Caching.Abstractions.csproj b/src/libraries/Microsoft.Extensions.Caching.Abstractions/ref/Microsoft.Extensions.Caching.Abstractions.csproj index 8619d10a530691..d3ee579507d087 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Abstractions/ref/Microsoft.Extensions.Caching.Abstractions.csproj +++ b/src/libraries/Microsoft.Extensions.Caching.Abstractions/ref/Microsoft.Extensions.Caching.Abstractions.csproj @@ -1,4 +1,4 @@ - + $(NetCoreAppCurrent);$(NetCoreAppPrevious);$(NetCoreAppMinimum);netstandard2.0;$(NetFrameworkMinimum) @@ -11,6 +11,9 @@ + + + GetOrCreateAsync( + System.ReadOnlySpan key, + System.Func> factory, + Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, + System.Collections.Generic.IEnumerable? tags = null, + System.Threading.CancellationToken cancellationToken = default) => throw null; + public virtual System.Threading.Tasks.ValueTask GetOrCreateAsync( + System.ReadOnlySpan key, + TState state, + System.Func> factory, + Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, + System.Collections.Generic.IEnumerable? tags = null, + System.Threading.CancellationToken cancellationToken = default) => throw null; + public System.Threading.Tasks.ValueTask GetOrCreateAsync( + ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler key, + System.Func> factory, + Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, + System.Collections.Generic.IEnumerable? tags = null, + System.Threading.CancellationToken cancellationToken = default) => throw null; + public System.Threading.Tasks.ValueTask GetOrCreateAsync( + ref System.Runtime.CompilerServices.DefaultInterpolatedStringHandler key, + TState state, + System.Func> factory, + Microsoft.Extensions.Caching.Hybrid.HybridCacheEntryOptions? options = null, + System.Collections.Generic.IEnumerable? tags = null, + System.Threading.CancellationToken cancellationToken = default) => throw null; + } +} diff --git a/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCache.cs b/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCache.cs index d1867d1090c0e8..40a7f5eff1e058 100644 --- a/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCache.cs +++ b/src/libraries/Microsoft.Extensions.Caching.Abstractions/src/Hybrid/HybridCache.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Distributed; @@ -45,6 +46,101 @@ public ValueTask GetOrCreateAsync(string key, Func? tags = null, CancellationToken cancellationToken = default) => GetOrCreateAsync(key, factory, WrappedCallbackCache.Instance, options, tags, cancellationToken); +#if NET10_0_OR_GREATER + /// + /// 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. + /// + /// The type of the data being considered. + /// The key of the entry to look for or create. + /// Provides the underlying data service if the data is not available in the cache. + /// Additional options for this cache entry. + /// The tags to associate with this cache item. + /// The used to propagate notifications that the operation should be canceled. + /// The data, either from cache or the underlying data service. + public ValueTask GetOrCreateAsync( + ReadOnlySpan key, + Func> factory, + HybridCacheEntryOptions? options = null, + IEnumerable? tags = null, + CancellationToken cancellationToken = default) + => GetOrCreateAsync(key, factory, WrappedCallbackCache.Instance, options, tags, cancellationToken); + + /// + /// 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. + /// + /// The type of additional state required by . + /// The type of the data being considered. + /// The key of the entry to look for or create. + /// Provides the underlying data service if the data is not available in the cache. + /// The state required for . + /// Additional options for this cache entry. + /// The tags to associate with this cache item. + /// The used to propagate notifications that the operation should be canceled. + /// The data, either from cache or the underlying data service. + /// Implementors may use the key span to attempt a local-cache synchronous 'get' without requiring the key as a . + public virtual ValueTask GetOrCreateAsync( + ReadOnlySpan key, + TState state, + Func> factory, + HybridCacheEntryOptions? options = null, + IEnumerable? tags = null, + CancellationToken cancellationToken = default) + => GetOrCreateAsync(key.ToString(), state, factory, options, tags, cancellationToken); + + /// + /// 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. + /// + /// The type of the data being considered. + /// The key of the entry to look for or create. + /// Provides the underlying data service if the data is not available in the cache. + /// Additional options for this cache entry. + /// The tags to associate with this cache item. + /// The used to propagate notifications that the operation should be canceled. + /// The data, either from cache or the underlying data service. + public ValueTask GetOrCreateAsync( + ref DefaultInterpolatedStringHandler key, + Func> factory, + HybridCacheEntryOptions? options = null, + IEnumerable? tags = null, + CancellationToken cancellationToken = default) + { + // It is *not* an error that this Clear occurs before the "await"; by definition, the implementation is *required* to copy + // the value locally before an await, precisely because the ref-struct cannot bridge an await. Thus: we are fine to clean + // the buffer even in the non-synchronous completion scenario. + ValueTask result = GetOrCreateAsync(key.Text, factory, WrappedCallbackCache.Instance, options, tags, cancellationToken); + key.Clear(); + return result; + } + + /// + /// 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. + /// + /// The type of additional state required by . + /// The type of the data being considered. + /// The key of the entry to look for or create. + /// Provides the underlying data service if the data is not available in the cache. + /// The state required for . + /// Additional options for this cache entry. + /// The tags to associate with this cache item. + /// The used to propagate notifications that the operation should be canceled. + /// The data, either from cache or the underlying data service. + public ValueTask GetOrCreateAsync( + ref DefaultInterpolatedStringHandler key, + TState state, + Func> factory, + HybridCacheEntryOptions? options = null, + IEnumerable? tags = null, + CancellationToken cancellationToken = default) + { + // It is *not* an error that this Clear occurs before the "await"; by definition, the implementation is *required* to copy + // the value locally before an await, precisely because the ref-struct cannot bridge an await. Thus: we are fine to clean + // the buffer even in the non-synchronous completion scenario. + ValueTask result = GetOrCreateAsync(key.Text, state, factory, options, tags, cancellationToken); + key.Clear(); + return result; + } +#endif + private static class WrappedCallbackCache // per-T memoized helper that allows GetOrCreateAsync and GetOrCreateAsync 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