dotnet/reactive · error · ArgumentNullException

removeHandler

Error message

removeHandler

What it means

System.ArgumentNullException with param name 'removeHandler'. The FromEventPattern(addHandler, removeHandler, scheduler) overload validates all delegate arguments up-front and throws when removeHandler is null. The library requires both subscription hooks to attach and detach the underlying CLR event via the FromEvent operator, so a missing remove handler would leave no way to unsubscribe.

Solutions

  1. Pass a non-null removeHandler delegate, e.g. h => target.Something -= h, mirroring the add handler.
  2. If the event truly cannot be unsubscribed, use a different FromEventPattern overload or wrap the event manually instead of passing null.
  3. Check argument order: (addHandler, removeHandler, scheduler) — a swapped or omitted argument leaves removeHandler null.
  4. Add an explicit null check with a clear message at your call site to fail fast before calling the library.

Example fix

// before
var obs = AsyncObservable.FromEventPattern(
    h => target.Something += h,
    null,
    scheduler);
// after
var obs = AsyncObservable.FromEventPattern(
    h => target.Something += h,
    h => target.Something -= h,
    scheduler);
Defensive patterns

Strategy: validation

Validate before calling

if (addHandler == null) throw new ArgumentNullException(nameof(addHandler));
if (removeHandler == null) throw new ArgumentNullException(nameof(removeHandler));
if (scheduler == null) scheduler = AsyncScheduler.Default;

Type guard

static bool IsValidFromEventPatternArgs(Action<EventHandler> add, Action<EventHandler> remove, IAsyncScheduler sched)
    => add != null && remove != null && sched != null;

Try / catch

try
{
    var obs = AsyncObservable.FromEventPattern(h => t.E += h, h => t.E -= h, scheduler);
}
catch (ArgumentNullException ex)
{
    // ex.ParamName tells which argument was null; log and fail fast
}

Prevention

When it happens

Trigger: Calling AsyncObservable.FromEventPattern(Action<EventHandler> addHandler, Action<EventHandler> removeHandler, IAsyncScheduler scheduler) and passing null for the removeHandler argument, e.g. FromEventPattern(h => target.Something += h, null, scheduler).

Common situations: Developers wire the add handler but skip the remove handler because the target has no symmetric -= API, or a refactored overload call accidentally drops one of the lambdas; also happens when the two lambdas are built dynamically and one comes back null.

Related errors


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

Appendix: source

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

using System.Globalization;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;

namespace System.Reactive.Linq
{
    public partial class AsyncObservable
    {
        public static IAsyncObservable<EventPattern<object>> FromEventPattern(Action<EventHandler> addHandler, Action<EventHandler> removeHandler) => FromEventPattern(addHandler, removeHandler, GetSchedulerForCurrentContext());

        public static IAsyncObservable<EventPattern<object>> FromEventPattern(Action<EventHandler> addHandler, Action<EventHandler> 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, object, EventArgs>(
                action => new EventHandler((o, e) => action(o, e)),
                addHandler,
                removeHandler,
                scheduler).Select(t => new EventPattern<object>(t.arg1, t.arg2));
        }

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

        public static IAsyncObservable<EventPattern<TEventArgs>> FromEventPattern<TDelegate, 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));

View on GitHub (pinned to 94b5d5ab91)