dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'removeHandler')

Error message

Value cannot be null. (Parameter 'removeHandler')

What it means

Observable.FromEventPattern(addHandler, removeHandler) also requires a non-null removeHandler so the subscription can detach on dispose. A null removeHandler throws ArgumentNullException at call time.

Solutions

  1. Pass a non-null detach lambda, e.g. h => target.Event -= h
  2. Verify reflection code obtains both GetAddMethod and GetRemoveMethod delegates
  3. Check wrapper methods for argument transposition that leaves removeHandler null

Example fix

// before
Observable.FromEventPattern(h => target.Click += h, null);
// after
Observable.FromEventPattern(h => target.Click += h, h => target.Click -= h);
Defensive patterns

Strategy: validation

Validate before calling

if (addHandler == null || removeHandler == null) throw new ArgumentNullException(removeHandler == null ? nameof(removeHandler) : nameof(addHandler));
var seq = Observable.FromEventPattern(addHandler, removeHandler);

Type guard

static bool HasRemoveHandler(Action<EventHandler> remove) => remove is not null;

Try / catch

try
{
    var seq = Observable.FromEventPattern(addHandler, removeHandler);
}
catch (ArgumentNullException ex) when (ex.ParamName == "removeHandler")
{
    throw new InvalidOperationException("Event remove accessor was not resolved", ex);
}

Prevention

When it happens

Trigger: Calling FromEventPattern(Action<EventHandler>, Action<EventHandler>) with a null second argument while addHandler is non-null.

Common situations: Reflection-resolved remove accessor that was null; partially initialized helper classes where only the add delegate was assigned; copy-paste code that set addHandler twice.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Linq/Observable.Events.cs:57

        /// making the Subscribe or Dispose call, respectively.
        /// </para>
        /// <para>
        /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so
        /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions
        /// more concise and easier to understand.
        /// </para>
        /// </remarks>
        /// <seealso cref="ToEventPattern"/>
        public static IObservable<EventPattern<object>> FromEventPattern(Action<EventHandler> addHandler, Action<EventHandler> removeHandler)
        {
            if (addHandler == null)
            {
                throw new ArgumentNullException(nameof(addHandler));
            }

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

            return s_impl.FromEventPattern(addHandler, removeHandler);
        }

        /// <summary>
        /// Converts a .NET event, conforming to the standard .NET event pattern based on <see cref="EventHandler"/>, to an observable sequence.
        /// Each event invocation is surfaced through an OnNext message in the resulting sequence.
        /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead.
        /// </summary>
        /// <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>
        /// <param name="scheduler">The scheduler to run the add and remove event handler logic on.</param>
        /// <returns>The observable sequence that contains data representations of invocations of the underlying .NET event.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="addHandler"/> or <paramref name="removeHandler"/> or <paramref name="scheduler"/> is null.</exception>
        /// <remarks>
        /// <para>
        /// Add and remove handler invocations are made whenever the number of observers grows beyond zero.

View on GitHub (pinned to 94b5d5ab91)