dotnet/reactive · error · ArgumentNullException

removeHandler

Error message

removeHandler

What it means

FromEventPattern throws ArgumentNullException when the removeHandler delegate is null. Both add and remove delegates are required so the observable can unsubscribe and avoid leaks when the observer disposes.

Solutions

  1. Provide a matching removeHandler delegate, typically (h) => target.EventName -= h
  2. Verify the same event is used for add and remove so disposal actually detaches
  3. Check refactored/reflection-based wiring that may produce null for the remove delegate
  4. If the event genuinely cannot be removed, wrap the handler in a no-op-safe lambda rather than passing null

Example fix

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

Strategy: validation

Validate before calling

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

Type guard

static bool HasRemoveHandler<T>(Action<T> remove) => remove != null;

Try / catch

try { var obs = Observable.FromEventPattern<TSender, TResult>(add, remove); }
catch (ArgumentNullException ex) when (ex.ParamName == "removeHandler") { /* supply unsubscribe lambda */ }

Prevention

When it happens

Trigger: Calling Observable.FromEventPattern<TSender,TResult>(addHandler, removeHandler) with a valid addHandler but a null removeHandler Action.

Common situations: Copy-paste wiring code where only the += lambda was written; conditional compilation left the unsubscribe path unimplemented; passing delegates built via reflection where the -='' MethodInfo was not found and null was cached.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Platforms/WinRT/Linq/WindowsObservable.Events.cs:35

        /// 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)