dotnet/reactive · error · ArgumentNullException

nameof(removeHandler)

Error message

nameof(removeHandler)

What it means

This is an ArgumentNullException thrown synchronously by the public AsyncRx FromEventPattern<TDelegate, TSender, TEventArgs>(addHandler, removeHandler, scheduler) overload when the removeHandler delegate is null. The library validates all delegate/scheduler arguments up front because the returned observable must be able to detach the handler on subscription disposal; without a remove handler the operator cannot guarantee cleanup. The nameof(removeHandler) in the message names the exact offending parameter.

Solutions

  1. Pass a non-null removeHandler delegate that detaches the same handler added by addHandler.
  2. If the event only supports add (no remove), use the FromEvent overload or a Subject-based bridge instead of FromEventPattern.
  3. Guard the call site with a null check and fail fast with a meaningful message before constructing the observable.

Example fix

// before
var obs = AsyncObservable.FromEventPattern<Action, object, EventArgs>(h => target.Fired += h, null, scheduler);
// after
var obs = AsyncObservable.FromEventPattern<Action, object, EventArgs>(h => target.Fired += h, h => target.Fired -= h, scheduler);
Defensive patterns

Strategy: validation

Validate before calling

if (addHandler is null) throw new ArgumentNullException(nameof(addHandler));
if (removeHandler is null) throw new ArgumentNullException(nameof(removeHandler));

Type guard

bool HasHandlerPair(A a, R r) => a is not null && r is not null;

Try / catch

try
{
    var obs = AsyncObservable.FromEventPattern<Action, object, EventArgs>(addHandler, removeHandler, scheduler);
}
catch (ArgumentNullException ex) when (ex.ParamName == nameof(removeHandler))
{
    logger.LogError(ex, "removeHandler must be provided to detach on unsubscribe");
}

Prevention

When it happens

Trigger: Calling AsyncObservable.FromEventPattern<TDelegate, TSender, TEventArgs> with (addHandler, null, scheduler) — e.g. passing a variable that was never assigned, or passing only the add delegate in a refactor that switched from a two-delegate to a null-tolerant API.

Common situations: Developers wiring .NET events to Rx who copy an addHandler expression and forget the matching removeHandler; reflection-built delegates where MethodInfo.CreateDelegate returned null; DI-registered handler accessors that resolved to null in tests.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/FromEventPattern.cs:71

                throw new ArgumentNullException(nameof(conversion));
            if (addHandler == null)
                throw new ArgumentNullException(nameof(addHandler));
            if (removeHandler == null)
                throw new ArgumentNullException(nameof(removeHandler));
            if (scheduler == null)
                throw new ArgumentNullException(nameof(scheduler));

            return FromEvent<TDelegate, object, TEventArgs>(action => conversion(new EventHandler<TEventArgs>((o, e) => action(o, e))), addHandler, removeHandler, scheduler).Select(t => new EventPattern<TEventArgs>(t.arg1, t.arg2));
        }

        public static IAsyncObservable<EventPattern<TSender, TEventArgs>> FromEventPattern<TDelegate, TSender, TEventArgs>(Action<TDelegate> addHandler, Action<TDelegate> removeHandler) => FromEventPattern<TDelegate, TSender, TEventArgs>(addHandler, removeHandler, GetSchedulerForCurrentContext());

        public static IAsyncObservable<EventPattern<TSender, TEventArgs>> FromEventPattern<TDelegate, TSender, TEventArgs>(Action<TDelegate> addHandler, Action<TDelegate> removeHandler, IAsyncScheduler scheduler)
        {
            if (addHandler == null)
                throw new ArgumentNullException(nameof(addHandler));
            if (removeHandler == null)
                throw new ArgumentNullException(nameof(removeHandler));
            if (scheduler == null)
                throw new ArgumentNullException(nameof(scheduler));

            return FromEvent<TDelegate, TSender, TEventArgs>(action => ConvertDelegate<Action<TSender, TEventArgs>, TDelegate>(action), addHandler, removeHandler, scheduler).Select(t => new EventPattern<TSender, TEventArgs>(t.arg1, t.arg2));
        }

        public static IAsyncObservable<EventPattern<TEventArgs>> FromEventPattern<TEventArgs>(Action<EventHandler<TEventArgs>> addHandler, Action<EventHandler<TEventArgs>> removeHandler) => FromEventPattern<TEventArgs>(addHandler, removeHandler, GetSchedulerForCurrentContext());

        public static IAsyncObservable<EventPattern<TEventArgs>> FromEventPattern<TEventArgs>(Action<EventHandler<TEventArgs>> addHandler, Action<EventHandler<TEventArgs>> removeHandler, IAsyncScheduler scheduler)
        {
            if (addHandler == null)
                throw new ArgumentNullException(nameof(addHandler));
            if (removeHandler == null)
                throw new ArgumentNullException(nameof(removeHandler));
            if (scheduler == null)
                throw new ArgumentNullException(nameof(scheduler));

            return FromEvent<EventHandler<TEventArgs>, object, TEventArgs>(action => ConvertDelegate<Action<object, TEventArgs>, EventHandler<TEventArgs>>(action), addHandler, removeHandler, scheduler).Select(t => new EventPattern<TEventArgs>(t.arg1, t.arg2));

View on GitHub (pinned to 94b5d5ab91)