dotnet/reactive · error · ArgumentNullException

Argument cannot be null (Parameter name: onNext)

Error message

Argument cannot be null (Parameter name: onNext)

What it means

ForEachAsync(IAsyncObservable<TSource>, Action<TSource>, CancellationToken) throws ArgumentNullException because the onNext action is null. The synchronous-callback overload requires a non-null Action<TSource> that is invoked for every element; the argument is validated up front with the parameter name 'onNext'. A null action cannot be interpreted as 'do nothing' — use an empty lambda instead.

Solutions

  1. Pass a real handler, e.g. x => Console.WriteLine(x) or an empty x => { } when nothing needs doing.
  2. If the handler is optional, coalesce: onNext ?? (x => { }).
  3. Ensure the delegate field/property is assigned before the pipeline is started.
  4. Validate handler presence at configuration time and reject the pipeline early with a clear message.

Example fix

// before
Action<int> handler = _handlers.Get("onNext"); // may be null
await source.ForEachAsync(handler);

// after
await source.ForEachAsync(_handlers.Get("onNext") ?? (x => { }));
Defensive patterns

Strategy: validation

Validate before calling

if (onNext == null)
    onNext = _ => { }; // or throw with context
return source.ForEachAsync(onNext, token);

Type guard

static bool IsNonNullHandler<T>(Action<T> h) => h is not null;

Try / catch

try { await source.ForEachAsync(onNext, token); }
catch (ArgumentNullException ex) when (ex.ParamName == "onNext") { /* attach default handler or report missing handler config */ }

Prevention

When it happens

Trigger: Calling source.ForEachAsync((Action<TSource>)null) — passing an unassigned delegate field, a callback parameter that the caller omitted, or a conditional handler assignment that stayed null.

Common situations: Event-handler wiring where the handler is attached later; plugin architectures where the user-supplied handler is optional; unit tests passing null to check behavior.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/ForEachAsync.cs:18

// 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;
using System.Threading.Tasks;

namespace System.Reactive.Linq
{
    public partial class AsyncObservable
    {
        public static Task ForEachAsync<TSource>(this IAsyncObservable<TSource> source, Action<TSource> onNext, CancellationToken token = default)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (onNext == null)
                throw new ArgumentNullException(nameof(onNext));

            return ForEachAsyncCore(source, (x, i) => { onNext(x); return Task.CompletedTask; }, token);
        }

        public static Task ForEachAsync<TSource>(this IAsyncObservable<TSource> source, Func<TSource, Task> onNext, CancellationToken token = default)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (onNext == null)
                throw new ArgumentNullException(nameof(onNext));

            return ForEachAsyncCore(source, (x, i) => onNext(x), token);
        }

        public static Task ForEachAsync<TSource>(this IAsyncObservable<TSource> source, Action<TSource, int> onNext, CancellationToken token = default)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));

View on GitHub (pinned to 94b5d5ab91)