Skip to content

Commit 44e2984

Browse files
committed
per feedback on #53255, try non-generic primary API
1 parent de07db3 commit 44e2984

File tree

5 files changed

+48
-31
lines changed

5 files changed

+48
-31
lines changed

src/Caching/Caching/src/DistributedCacheServiceExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static IServiceCollection AddTypedDistributedCache(this IServiceCollectio
2222
services.AddDistributedMemoryCache(); // we need a backend; use in-proc by default
2323
services.TryAddSingleton(typeof(ICacheSerializer<string>), typeof(StringSerializer));
2424
services.TryAddSingleton(typeof(ICacheSerializer<>), typeof(DefaultJsonSerializer<>));
25-
services.AddSingleton(typeof(IDistributedCache<>), typeof(DistributedCache<>));
25+
services.AddSingleton(typeof(IAdvancedDistributedCache), typeof(DistributedCache));
2626
return services;
2727
}
2828
}

src/Caching/Caching/src/DistributedCacheT.cs

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,48 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Buffers;
56
using Microsoft.AspNetCore.OutputCaching;
67
using Microsoft.Extensions.Options;
78

89
namespace Microsoft.Extensions.Caching.Distributed;
9-
internal sealed class DistributedCache<T> : IDistributedCache<T>
10+
internal sealed class DistributedCache : IAdvancedDistributedCache
1011
{
11-
private readonly ICacheSerializer<T> _serializer;
12+
private readonly IServiceProvider _services;
1213
private readonly IDistributedCache _backend;
1314
private readonly IBufferDistributedCache? _bufferBackend;
1415

15-
public DistributedCache(IOptions<TypedDistributedCacheOptions> options, ICacheSerializer<T> serializer, IDistributedCache backend)
16+
public DistributedCache(IOptions<TypedDistributedCacheOptions> options, IServiceProvider services, IDistributedCache backend)
1617
{
17-
_serializer = serializer;
18+
_services = services;
1819
_backend = backend;
1920
_bufferBackend = backend as IBufferDistributedCache; // do the type test once only
2021
_ = options;
2122
}
2223

23-
// 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
24-
static readonly Func<Func<CancellationToken, ValueTask<T>>, CancellationToken, ValueTask<T>> _wrapped = static (callback, ct) => callback(ct);
25-
public ValueTask<T> GetAsync(string key, Func<CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options = null, CancellationToken cancellationToken = default)
26-
=> GetAsync(key, callback, _wrapped, options, cancellationToken);
24+
static class WrappedCallbackCache<T>
25+
{
26+
// 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
27+
public static readonly Func<Func<CancellationToken, ValueTask<T>>, CancellationToken, ValueTask<T>> Instance = static (callback, ct) => callback(ct);
28+
29+
}
30+
public ValueTask<T> GetAsync<T>(string key, Func<CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options = null, CancellationToken cancellationToken = default)
31+
=> GetAsync(key, callback, WrappedCallbackCache<T>.Instance, options, cancellationToken);
2732

28-
public ValueTask<T> GetAsync<TState>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options = null, CancellationToken cancellationToken = default)
33+
public ValueTask<T> GetAsync<TState, T>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options = null, CancellationToken cancellationToken = default)
2934
{
3035
ArgumentException.ThrowIfNullOrWhiteSpace(key);
3136
ArgumentNullException.ThrowIfNull(callback);
3237

3338
return _bufferBackend is not null
3439
? GetBufferedBackendAsync(key, state, callback, options, cancellationToken)
3540
: GetLegacyBackendAsync(key, state, callback, options, cancellationToken);
36-
3741
}
3842

39-
ValueTask IDistributedCache<T>.RefreshAsync(string key, CancellationToken cancellationToken) => new(_backend.RefreshAsync(key, cancellationToken));
43+
ValueTask IAdvancedDistributedCache.RefreshAsync(string key, CancellationToken cancellationToken) => new(_backend.RefreshAsync(key, cancellationToken));
4044

41-
private ValueTask<T> GetBufferedBackendAsync<TState>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options, CancellationToken cancellationToken)
45+
private ValueTask<T> GetBufferedBackendAsync<TState, T>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options, CancellationToken cancellationToken)
4246
{
4347
var buffer = new RecyclableArrayBufferWriter<byte>();
4448
var pendingGet = _bufferBackend!.TryGetAsync(key, buffer, cancellationToken);
@@ -51,22 +55,22 @@ private ValueTask<T> GetBufferedBackendAsync<TState>(string key, TState state, F
5155
// fast path; backend available immediately
5256
if (pendingGet.GetAwaiter().GetResult())
5357
{
54-
var result = _serializer.Deserialize(new(buffer.GetCommittedMemory()));
58+
var result = GetSerializer<T>().Deserialize(new(buffer.GetCommittedMemory()));
5559
buffer.Dispose();
5660
return new(result);
5761
}
5862

5963
// fall back to main code-path, but without the pending bytes (we've already checked those)
6064
return AwaitedBackend(this, key, state, callback, options, cancellationToken, buffer, default);
6165

62-
static async ValueTask<T> AwaitedBackend(DistributedCache<T> @this, string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options,
66+
static async ValueTask<T> AwaitedBackend(DistributedCache @this, string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options,
6367
CancellationToken cancellationToken, RecyclableArrayBufferWriter<byte> buffer, ValueTask<bool> pendingGet)
6468
{
6569
using (buffer)
6670
{
6771
if (await pendingGet)
6872
{
69-
return @this._serializer.Deserialize(new(buffer.GetCommittedMemory()));
73+
return @this.GetSerializer<T>().Deserialize(new(buffer.GetCommittedMemory()));
7074
}
7175

7276
var value = await callback(state, cancellationToken);
@@ -77,7 +81,7 @@ static async ValueTask<T> AwaitedBackend(DistributedCache<T> @this, string key,
7781
else
7882
{
7983
buffer.Reset();
80-
@this._serializer.Serialize(value, buffer);
84+
@this.GetSerializer<T>().Serialize(value, buffer);
8185
await @this._bufferBackend!.SetAsync(key, new(buffer.GetCommittedMemory()), options ?? _defaultOptions, cancellationToken);
8286
}
8387

@@ -86,7 +90,20 @@ static async ValueTask<T> AwaitedBackend(DistributedCache<T> @this, string key,
8690
}
8791
}
8892

89-
private ValueTask<T> GetLegacyBackendAsync<TState>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options, CancellationToken cancellationToken)
93+
private ICacheSerializer<T> GetSerializer<T>()
94+
{
95+
var obj = (ICacheSerializer<T>?)_services.GetService(typeof(ICacheSerializer<T>));
96+
if (obj is null)
97+
{
98+
ThrowNoSerializer(typeof(T));
99+
}
100+
return obj!;
101+
102+
}
103+
104+
static void ThrowNoSerializer(Type type) => throw new InvalidOperationException("No serializer registered for " + type.FullName);
105+
106+
private ValueTask<T> GetLegacyBackendAsync<TState, T>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options, CancellationToken cancellationToken)
90107
{
91108
var pendingBytes = _backend.GetAsync(key, cancellationToken);
92109
if (!pendingBytes.IsCompletedSuccessfully)
@@ -98,21 +115,21 @@ private ValueTask<T> GetLegacyBackendAsync<TState>(string key, TState state, Fun
98115
var bytes = pendingBytes.Result;
99116
if (bytes is not null)
100117
{
101-
return new(_serializer.Deserialize(new ReadOnlySequence<byte>(bytes))!);
118+
return new(GetSerializer<T>().Deserialize(new ReadOnlySequence<byte>(bytes))!);
102119
}
103120

104121
// fall back to main code-path, but without the pending bytes (we've already checked those)
105122
return AwaitedBackend(this, key, state, callback, options, cancellationToken, null);
106123

107-
static async ValueTask<T> AwaitedBackend(DistributedCache<T> @this, string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options,
124+
static async ValueTask<T> AwaitedBackend(DistributedCache @this, string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options,
108125
CancellationToken cancellationToken, Task<byte[]?>? pendingBytes)
109126
{
110127
if (pendingBytes is not null)
111128
{
112129
var bytes = await pendingBytes;
113130
if (bytes is not null)
114131
{
115-
return @this._serializer.Deserialize(new ReadOnlySequence<byte>(bytes));
132+
return @this.GetSerializer<T>().Deserialize(new ReadOnlySequence<byte>(bytes));
116133
}
117134
}
118135

@@ -124,7 +141,7 @@ static async ValueTask<T> AwaitedBackend(DistributedCache<T> @this, string key,
124141
else
125142
{
126143
using var writer = new RecyclableArrayBufferWriter<byte>();
127-
@this._serializer.Serialize(value, writer);
144+
@this.GetSerializer<T>().Serialize(value, writer);
128145
await @this._backend.SetAsync(key, writer.ToArray(), options ?? _defaultOptions, cancellationToken);
129146
}
130147

src/Caching/Caching/src/IDistributedCacheT.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55

66
namespace Microsoft.Extensions.Caching.Distributed;
77

8-
public interface IDistributedCache<T>
8+
public interface IAdvancedDistributedCache
99
{
1010
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Does not cause ambiguity due to callback signature delta")]
11-
ValueTask<T> GetAsync(string key, Func<CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options = null, CancellationToken cancellationToken = default);
11+
ValueTask<T> GetAsync<T>(string key, Func<CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options = null, CancellationToken cancellationToken = default);
1212

1313
[SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Does not cause ambiguity due to callback signature delta")]
14-
ValueTask<T> GetAsync<TState>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options = null, CancellationToken cancellationToken = default);
14+
ValueTask<T> GetAsync<TState, T>(string key, TState state, Func<TState, CancellationToken, ValueTask<T>> callback, DistributedCacheEntryOptions? options = null, CancellationToken cancellationToken = default);
1515

1616
ValueTask RemoveAsync(string key, CancellationToken cancellationToken = default);
1717

src/Caching/Caching/src/PublicAPI.Unshipped.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
#nullable enable
22
Microsoft.Extensions.Caching.Distributed.DistributedCacheServiceExtensions
3+
Microsoft.Extensions.Caching.Distributed.IAdvancedDistributedCache
4+
Microsoft.Extensions.Caching.Distributed.IAdvancedDistributedCache.GetAsync<T>(string! key, System.Func<System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<T>>! callback, Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<T>
5+
Microsoft.Extensions.Caching.Distributed.IAdvancedDistributedCache.GetAsync<TState, T>(string! key, TState state, System.Func<TState, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<T>>! callback, Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<T>
6+
Microsoft.Extensions.Caching.Distributed.IAdvancedDistributedCache.RefreshAsync(string! key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
7+
Microsoft.Extensions.Caching.Distributed.IAdvancedDistributedCache.RemoveAsync(string! key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
38
Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache
49
Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.SetAsync(string! key, System.Buffers.ReadOnlySequence<byte> value, Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions! options, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask
510
Microsoft.Extensions.Caching.Distributed.IBufferDistributedCache.TryGetAsync(string! key, System.Buffers.IBufferWriter<byte>! destination, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask<bool>
611
Microsoft.Extensions.Caching.Distributed.ICacheSerializer<T>
712
Microsoft.Extensions.Caching.Distributed.ICacheSerializer<T>.Deserialize(System.Buffers.ReadOnlySequence<byte> source) -> T
813
Microsoft.Extensions.Caching.Distributed.ICacheSerializer<T>.Serialize(T value, System.Buffers.IBufferWriter<byte>! target) -> void
9-
Microsoft.Extensions.Caching.Distributed.IDistributedCache<T>
10-
Microsoft.Extensions.Caching.Distributed.IDistributedCache<T>.GetAsync(string! key, System.Func<System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<T>>! callback, Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<T>
11-
Microsoft.Extensions.Caching.Distributed.IDistributedCache<T>.GetAsync<TState>(string! key, TState state, System.Func<TState, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<T>>! callback, Microsoft.Extensions.Caching.Distributed.DistributedCacheEntryOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<T>
12-
Microsoft.Extensions.Caching.Distributed.IDistributedCache<T>.RefreshAsync(string! key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
13-
Microsoft.Extensions.Caching.Distributed.IDistributedCache<T>.RemoveAsync(string! key, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask
1414
Microsoft.Extensions.Caching.Distributed.IDistributedCacheInvalidation
1515
Microsoft.Extensions.Caching.Distributed.IDistributedCacheInvalidation.CacheKeyInvalidated -> System.Func<string!, System.Threading.Tasks.ValueTask>!
1616
Microsoft.Extensions.Caching.Distributed.TypedDistributedCacheOptions

src/Caching/Caching/test/CacheConfigTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ public async Task StatefulUsage(bool useCustomBackend)
104104
}
105105
}
106106

107-
public class SomeService(IDistributedCache<Foo> cache)
107+
public class SomeService(IAdvancedDistributedCache cache)
108108
{
109109
private int _backendCalls;
110110
public int BackendCalls => _backendCalls;

0 commit comments

Comments
 (0)