dotnet/reactive · error · ArgumentNullException

nameof(finallyAction)

Error message

nameof(finallyAction)

What it means

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.

Solutions

  1. Ensure a non-null Action is passed; if no work is needed on termination, omit the Finally call entirely rather than passing null
  2. If the delegate is conditional, coalesce to a no-op: source.Finally(() => { })
  3. Check the variable holding the callback for initialization/null propagation issues before calling Finally

Example fix

// before
IAsyncObservable<int> res = source.Finally(cleanupAction); // cleanupAction is null
// after
if (cleanupAction == null)
    return source; // nothing to do on termination
IAsyncObservable<int> res = source.Finally(cleanupAction);
Defensive patterns

Strategy: validation

Validate before calling

if (source == null) throw new ArgumentNullException(nameof(source));
if (finallyAction == null) throw new ArgumentNullException(nameof(finallyAction));
var result = source.Finally(finallyAction);

Type guard

bool IsValidFinally(Action? a) => a is not null;

Try / catch

try
{
    var result = source.Finally(finallyAction);
}
catch (ArgumentNullException ex) when (ex.ParamName == nameof(finallyAction))
{
    result = source; // skip the termination hook
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/87083dc50f3efa49. Report an issue: GitHub.

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/Finally.cs:17

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT License.
// See the LICENSE file in the project root for more information. 

using System.Reactive.Disposables;
using System.Threading.Tasks;

namespace System.Reactive.Linq
{
    public partial class AsyncObservable
    {
        public static IAsyncObservable<TSource> Finally<TSource>(this IAsyncObservable<TSource> source, Action finallyAction)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (finallyAction == null)
                throw new ArgumentNullException(nameof(finallyAction));

            return Create(
                source,
                finallyAction,
                static async (source, finallyAction, observer) =>
                {
                    var subscription = await source.SubscribeSafeAsync(observer).ConfigureAwait(false);

                    return AsyncDisposable.Create(async () =>
                    {
                        try
                        {
                            await subscription.DisposeAsync().ConfigureAwait(false);
                        }
                        finally
                        {
                            finallyAction();
                        }

View on GitHub (pinned to 94b5d5ab91)