diff --git a/src/Polly/Caching/CachePolicy.cs b/src/Polly/Caching/CachePolicy.cs
index e36ccf0d072..e1eed65bd77 100644
--- a/src/Polly/Caching/CachePolicy.cs
+++ b/src/Polly/Caching/CachePolicy.cs
@@ -4,7 +4,6 @@ namespace Polly.Caching;
///
/// A cache policy that can be applied to the results of delegate executions.
///
-#pragma warning disable CA1062 // Validate arguments of public methods
public class CachePolicy : Policy, ICachePolicy
{
private readonly ISyncCacheProvider _syncCacheProvider;
@@ -39,14 +38,27 @@ internal CachePolicy(
}
///
- protected override void Implementation(Action action, Context context, CancellationToken cancellationToken) // Pass-through/NOOP policy action, for void-returning calls through a cache policy.
- =>
- action(context, cancellationToken);
+ protected override void Implementation(Action action, Context context, CancellationToken cancellationToken)
+ {
+ // Pass-through/NOOP policy action, for void-returning calls through a cache policy.
+ if (action is null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ action(context, cancellationToken);
+ }
///
[DebuggerStepThrough]
- protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) =>
- CacheEngine.Implementation(
+ protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken)
+ {
+ if (action is null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ return CacheEngine.Implementation(
_syncCacheProvider.For(),
_ttlStrategy.For(),
_cacheKeyStrategy,
@@ -58,6 +70,7 @@ protected override TResult Implementation(Func
@@ -99,8 +112,14 @@ internal CachePolicy(
///
[DebuggerStepThrough]
- protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken) =>
- CacheEngine.Implementation(
+ protected override TResult Implementation(Func action, Context context, CancellationToken cancellationToken)
+ {
+ if (action is null)
+ {
+ throw new ArgumentNullException(nameof(action));
+ }
+
+ return CacheEngine.Implementation(
_syncCacheProvider,
_ttlStrategy,
_cacheKeyStrategy,
@@ -112,4 +131,5 @@ protected override TResult Implementation(Func action = null!;
+ Action actionVoid = null!;
+
+ ISyncCacheProvider syncCacheProvider = new StubCacheProvider();
+ ITtlStrategy ttlStrategy = new ContextualTtl();
+ Func cacheKeyStrategy = (_) => string.Empty;
+ Action onCacheGet = (_, _) => { };
+ Action onCacheMiss = (_, _) => { };
+ Action onCachePut = (_, _) => { };
+ Action? onCacheGetError = null;
+ Action? onCachePutError = null;
+
+ var instance = Activator.CreateInstance(
+ typeof(CachePolicy),
+ flags,
+ null,
+ [
+ syncCacheProvider,
+ ttlStrategy,
+ cacheKeyStrategy,
+ onCacheGet,
+ onCacheMiss,
+ onCachePut,
+ onCacheGetError,
+ onCachePutError,
+ ],
+ null)!;
+ var instanceType = instance.GetType();
+ var methods = instanceType.GetMethods(flags);
+ var methodInfo = methods.First(method => method is { Name: "Implementation", ReturnType.Name: "TResult" });
+ var generic = methodInfo.MakeGenericMethod(typeof(EmptyStruct));
+
+ var func = () => generic.Invoke(instance, [action, new Context(), CancellationToken.None]);
+
+ var exceptionAssertions = func.Should().Throw();
+ exceptionAssertions.And.Message.Should().Be("Exception has been thrown by the target of an invocation.");
+ exceptionAssertions.And.InnerException.Should().BeOfType()
+ .Which.ParamName.Should().Be("action");
+
+ methodInfo = methods.First(method => method is { Name: "Implementation", ReturnType.Name: "Void" });
+
+ func = () => methodInfo.Invoke(instance, [actionVoid, new Context(), CancellationToken.None]);
+
+ exceptionAssertions = func.Should().Throw();
+ exceptionAssertions.And.Message.Should().Be("Exception has been thrown by the target of an invocation.");
+ exceptionAssertions.And.InnerException.Should().BeOfType()
+ .Which.ParamName.Should().Be("action");
+ }
+
[Fact]
public void Should_throw_when_cache_provider_is_null()
{
diff --git a/test/Polly.Specs/Caching/CacheTResultSpecs.cs b/test/Polly.Specs/Caching/CacheTResultSpecs.cs
index ab4227531a9..15e7f278ce0 100644
--- a/test/Polly.Specs/Caching/CacheTResultSpecs.cs
+++ b/test/Polly.Specs/Caching/CacheTResultSpecs.cs
@@ -5,6 +5,48 @@ public class CacheTResultSpecs : IDisposable
{
#region Configuration
+ [Fact]
+ public void Should_throw_when_action_is_null()
+ {
+ var flags = BindingFlags.NonPublic | BindingFlags.Instance;
+ Func action = null!;
+
+ ISyncCacheProvider syncCacheProvider = new StubCacheProvider().For();
+ ITtlStrategy ttlStrategy = new ContextualTtl().For();
+ Func cacheKeyStrategy = (_) => string.Empty;
+ Action onCacheGet = (_, _) => { };
+ Action onCacheMiss = (_, _) => { };
+ Action onCachePut = (_, _) => { };
+ Action? onCacheGetError = null;
+ Action? onCachePutError = null;
+
+ var instance = Activator.CreateInstance(
+ typeof(CachePolicy),
+ flags,
+ null,
+ [
+ syncCacheProvider,
+ ttlStrategy,
+ cacheKeyStrategy,
+ onCacheGet,
+ onCacheMiss,
+ onCachePut,
+ onCacheGetError,
+ onCachePutError,
+ ],
+ null)!;
+ var instanceType = instance.GetType();
+ var methods = instanceType.GetMethods(flags);
+ var methodInfo = methods.First(method => method is { Name: "Implementation", ReturnType.Name: "EmptyStruct" });
+
+ var func = () => methodInfo.Invoke(instance, [action, new Context(), CancellationToken.None]);
+
+ var exceptionAssertions = func.Should().Throw();
+ exceptionAssertions.And.Message.Should().Be("Exception has been thrown by the target of an invocation.");
+ exceptionAssertions.And.InnerException.Should().BeOfType()
+ .Which.ParamName.Should().Be("action");
+ }
+
[Fact]
public void Should_throw_when_cache_provider_is_null()
{