{"record":{"id":"487300954a12b947","repo":"App-vNext/Polly","slug":"value-cannot-be-null-487300","errorCode":null,"errorMessage":"Value cannot be null.","messagePattern":"Value cannot be null\\.","errorType":"exception","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"src/Polly/Caching/AsyncCachePolicy.cs","lineNumber":49,"sourceCode":"        _cacheKeyStrategy = cacheKeyStrategy;\n\n        _onCacheGet = onCacheGet;\n        _onCachePut = onCachePut;\n        _onCacheMiss = onCacheMiss;\n        _onCacheGetError = onCacheGetError;\n        _onCachePutError = onCachePutError;\n    }\n\n    /// <inheritdoc/>\n    protected override Task ImplementationAsync(\n        Func<Context, CancellationToken, Task> action,\n        Context context,\n        CancellationToken cancellationToken,\n        bool continueOnCapturedContext)\n    {\n        if (action is null)\n        {\n            throw new ArgumentNullException(nameof(action));\n        }\n\n        // Pass-through/NOOP policy action, for void-returning executions through the cache policy.\n        return action(context, cancellationToken);\n    }\n\n    /// <inheritdoc/>\n    [DebuggerStepThrough]\n    protected override Task<TResult> ImplementationAsync<TResult>(\n        Func<Context, CancellationToken, Task<TResult>> action,\n        Context context,\n        CancellationToken cancellationToken,\n        bool continueOnCapturedContext)\n    {\n        if (action is null)\n        {\n            throw new ArgumentNullException(nameof(action));\n        }","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/App-vNext/Polly/blob/d0e46bdb1ee11ea50d0e4b6846d2633d6bc09bac/src/Polly/Caching/AsyncCachePolicy.cs#L31-L67","documentation":"Thrown as ArgumentNullException(nameof(action)) from the non-generic AsyncCachePolicy.ImplementationAsync (the void-returning overload at line 41–54). This overload is a pass-through/NOOP for void executions through the cache policy — it does not actually cache anything, it just validates and invokes the action directly. The null guard at line 47–50 fires before invocation.","triggerScenarios":"Calling policy.ExecuteAsync(null) on an AsyncCachePolicy where the delegate returns Task (void) rather than Task<TResult>. The pass-through ImplementationAsync at line 41 catches the null action before any cache interaction.","commonSituations":"Passing a null async delegate to a cache policy's fire-and-forget execution path. Refactoring that moved the delegate construction to a conditional path that returns null. Misunderstanding that void-returning cache executions are pass-through and not cached.","solutions":["Ensure the async delegate passed to ExecuteAsync is non-null","Guard the call site with a null check before invoking","Use nullable reference types to surface null delegate issues at compile time"],"exampleFix":"// before\nFunc<CancellationToken, Task> doWork = GetWorkAsync(); // may be null\nawait cachePolicy.ExecuteAsync(doWork); // ArgumentNullException\n\n// after\nFunc<CancellationToken, Task> doWork = GetWorkAsync();\nif (doWork is not null)\n{\n    await cachePolicy.ExecuteAsync(doWork);\n}","handlingStrategy":"validation","validationCode":"if (action is null)\n{\n    throw new InvalidOperationException(\"Async action must be provided.\");\n}\nawait cachePolicy.ExecuteAsync(action);","typeGuard":"public static bool IsExecutableAsyncDelegate(Func<CancellationToken, Task> action) => action is not null;","tryCatchPattern":null,"preventionTips":["Enable nullable reference types to surface null delegates at compile time","Use GetRequiredService for delegate sources from DI","Avoid conditional delegate assignment without a fallback"],"tags":["cache","null-argument","action","async","argument-validation"],"backgroundTag":null,"analyzedSha":"d0e46bdb1ee11ea50d0e4b6846d2633d6bc09bac","analyzedAt":"2026-08-13T16:36:01.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}