dotnet/reactive · error · ArgumentNullException

ArgumentNullException: removeHandler

Error message

ArgumentNullException: removeHandler

What it means

In FromEventPattern<TSender, TResult>(addHandler, removeHandler), after validating addHandler the method validates removeHandler and throws ArgumentNullException("removeHandler") when it is null. Both delegates are required so the observable can attach on Subscribe and detach on Dispose.

Solutions

  1. Provide the real remove accessor: h => target.Event -= h.
  2. If reflection fails to find the remove method, surface a descriptive error rather than passing null.
  3. Never substitute null for unsubscribe; a no-op remove delegate (h => { }) at least avoids the throw, but prefer correct detachment to avoid leaks.

Example fix

// before
Observable.FromEventPattern<Button, object>(
    h => button.Click += h,
    null); // throws
// after
Observable.FromEventPattern<Button, object>(
    h => button.Click += h,
    h => button.Click -= h);
Defensive patterns

Strategy: validation

Validate before calling

if (removeHandler == null)
    throw new InvalidOperationException("removeHandler required to detach on dispose");

Type guard

bool HasRemove<TH>(Action<TH>? remove) => remove is not null;

Try / catch

try { obs = Observable.FromEventPattern<TSender, TResult>(add, remove); }
catch (ArgumentNullException ex) when (ex.ParamName == "removeHandler") { log.Error("remove accessor missing; subscription would leak"); }

Prevention

When it happens

Trigger: Calling Observable.FromEventPattern<TSender, TResult>(addHandler, null) — a null remove delegate, commonly from reflection lookups that failed to find the remove accessor, or building add/remove pairs where only add was supplied.

Common situations: Reflection-based wiring missing the remove accessor (GetRemoveMethod returned null); event objects exposed through wrappers where unsubscribe isn't implemented and null was passed instead; copy-paste errors omitting the second delegate.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive.WindowsRuntime/System.Reactive.Linq/WindowsObservable.Events.cs:38

        /// Converts a typed event, conforming to the standard event pattern, to an observable sequence.
        /// </summary>
        /// <typeparam name="TSender">The type of the sender that raises the event.</typeparam>
        /// <typeparam name="TResult">The type of the event data generated by the event.</typeparam>
        /// <param name="addHandler">Action that attaches the given event handler to the underlying .NET event.</param>
        /// <param name="removeHandler">Action that detaches the given event handler from the underlying .NET event.</param>
        /// <returns>The observable sequence that contains data representations of invocations of the underlying typed event.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="addHandler"/> or <paramref name="removeHandler"/> is null.</exception>
        /// <seealso cref="ToEventPattern"/>
        public static IObservable<EventPattern<TSender, TResult>> FromEventPattern<TSender, TResult>(Action<TypedEventHandler<TSender, TResult>> addHandler, Action<TypedEventHandler<TSender, TResult>> removeHandler)
        {
            if (addHandler == null)
            {
                throw new ArgumentNullException(nameof(addHandler));
            }

            if (removeHandler == null)
            {
                throw new ArgumentNullException(nameof(removeHandler));
            }

            return Observable.Create<EventPattern<TSender, TResult>>(observer =>
            {
                var h = new TypedEventHandler<TSender, TResult>((sender, args) =>
                {
                    observer.OnNext(new EventPattern<TSender, TResult>(sender, args));
                });

                addHandler(h);

                return () =>
                {
                    removeHandler(h);
                };
            });
        }

View on GitHub (pinned to 94b5d5ab91)