{"record":{"id":"87083dc50f3efa49","repo":"dotnet/reactive","slug":"nameof-finallyaction","errorCode":null,"errorMessage":"nameof(finallyAction)","messagePattern":"nameof\\(finallyAction\\)","errorType":"exception","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"AsyncRx.NET/System.Reactive.Async/Linq/Operators/Finally.cs","lineNumber":17,"sourceCode":"﻿// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT License.\n// See the LICENSE file in the project root for more information. \n\nusing System.Reactive.Disposables;\nusing System.Threading.Tasks;\n\nnamespace System.Reactive.Linq\n{\n    public partial class AsyncObservable\n    {\n        public static IAsyncObservable<TSource> Finally<TSource>(this IAsyncObservable<TSource> source, Action finallyAction)\n        {\n            if (source == null)\n                throw new ArgumentNullException(nameof(source));\n            if (finallyAction == null)\n                throw new ArgumentNullException(nameof(finallyAction));\n\n            return Create(\n                source,\n                finallyAction,\n                static async (source, finallyAction, observer) =>\n                {\n                    var subscription = await source.SubscribeSafeAsync(observer).ConfigureAwait(false);\n\n                    return AsyncDisposable.Create(async () =>\n                    {\n                        try\n                        {\n                            await subscription.DisposeAsync().ConfigureAwait(false);\n                        }\n                        finally\n                        {\n                            finallyAction();\n                        }","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/AsyncRx.NET/System.Reactive.Async/Linq/Operators/Finally.cs#L1-L35","documentation":"This is an ArgumentNullException thrown by the Finally operator when the finallyAction delegate is null. The Finally operator requires an Action to invoke after the source sequence terminates (normally, with error, or on disposal), so it validates the delegate up front before building the pipeline. Passing null means the operator could not guarantee its post-termination hook.","triggerScenarios":"Calling source.Finally(null) — the Action overload — on an IAsyncObservable<TSource>. Typically caused by a variable holding the callback being null (optional callback not supplied), a factory method returning null, or a refactor that renamed a method and left a null delegate.","commonSituations":"Conditional instrumentation/teardown code where the cleanup action is only assigned in some code paths; DI-registered callback services that are null in test environments; passing the result of a method that returns null instead of a no-op.","solutions":["Ensure a non-null Action is passed; if no work is needed on termination, omit the Finally call entirely rather than passing null","If the delegate is conditional, coalesce to a no-op: source.Finally(() => { })","Check the variable holding the callback for initialization/null propagation issues before calling Finally"],"exampleFix":"// before\nIAsyncObservable<int> res = source.Finally(cleanupAction); // cleanupAction is null\n// after\nif (cleanupAction == null)\n    return source; // nothing to do on termination\nIAsyncObservable<int> res = source.Finally(cleanupAction);","handlingStrategy":"validation","validationCode":"if (source == null) throw new ArgumentNullException(nameof(source));\nif (finallyAction == null) throw new ArgumentNullException(nameof(finallyAction));\nvar result = source.Finally(finallyAction);","typeGuard":"bool IsValidFinally(Action? a) => a is not null;","tryCatchPattern":"try\n{\n    var result = source.Finally(finallyAction);\n}\ncatch (ArgumentNullException ex) when (ex.ParamName == nameof(finallyAction))\n{\n    result = source; // skip the termination hook\n}","preventionTips":["Never pass null for optional cleanup — omit the Finally operator instead","Default optional delegates to a no-op lambda: () => { }","Validate delegates at the pipeline's entry point, not inside operators"],"tags":["csharp","null-argument","argument-validation","async-rx"],"backgroundTag":"null-argument","analyzedSha":"94b5d5ab912789f5abe9a72138a25bbd716fe59c","analyzedAt":"2026-09-15T02:26:24.759Z","contentChangedAt":"2026-09-15T02:26:24.759Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}