dotnet/reactive · error · ArgumentNullException

Thrown when finallyAction is null (ArgumentNullException…

Error message

Thrown when finallyAction is null (ArgumentNullException, param name: finallyAction)

What it means

Finally throws ArgumentNullException when finallyAction is null. The whole point of the operator is to invoke this action reliably, so a null action is invalid and rejected eagerly with paramName 'finallyAction'.

Solutions

  1. Provide a no-op () => {} when cleanup is genuinely optional.
  2. Ensure the cleanup delegate is registered/initialized before the call.
  3. Guard the delegate source with a default.

Example fix

// before
var seq = source.Finally(cleanup); // cleanup may be null
// after
var seq = source.Finally(cleanup ?? (() => { }));
Defensive patterns

Strategy: validation

Validate before calling

if (finallyAction is null) throw new ArgumentNullException(nameof(finallyAction));
Finally(source, finallyAction ?? (() => { }));

Type guard

bool HasAction(Action a) => a is not null;

Try / catch

try { var q = source.Finally(action); }
catch (ArgumentNullException ex) when (ex.ParamName == "finallyAction") { /* register or default the action */ }

Prevention

When it happens

Trigger: Calling Finally(source, null), e.g. a cleanup Action resolved from an optional member or DI registration that is missing.

Common situations: Optional cleanup callback not registered; passing a null delegate from configuration; refactoring removed the handler but kept the call.

Related errors


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

Appendix: source

Thrown at Ix.NET/Source/System.Interactive/System/Linq/Operators/Finally.cs:23

using System.Collections.Generic;

namespace System.Linq
{
    public static partial class EnumerableEx
    {
        /// <summary>
        /// Creates a sequence whose termination or disposal of an enumerator causes a finally action to be executed.
        /// </summary>
        /// <typeparam name="TSource">Source sequence element type.</typeparam>
        /// <param name="source">Source sequence.</param>
        /// <param name="finallyAction">Action to run upon termination of the sequence, or when an enumerator is disposed.</param>
        /// <returns>Source sequence with guarantees on the invocation of the finally action.</returns>
        public static IEnumerable<TSource> Finally<TSource>(this IEnumerable<TSource> source, Action finallyAction)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (finallyAction == null)
                throw new ArgumentNullException(nameof(finallyAction));

            return FinallyCore(source, finallyAction);
        }

        private static IEnumerable<TSource> FinallyCore<TSource>(IEnumerable<TSource> source, Action finallyAction)
        {
            try
            {
                foreach (var item in source)
                {
                    yield return item;
                }
            }
            finally
            {
                finallyAction();
            }
        }

View on GitHub (pinned to 94b5d5ab91)