dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'addHandler')

Error message

Value cannot be null. (Parameter 'addHandler')

What it means

Observable.FromEventPattern(addHandler, removeHandler) needs delegates that attach and detach the .NET event handler. A null addHandler is rejected immediately with ArgumentNullException because subscription could not wire up the event.

Solutions

  1. Pass non-null lambdas such as h => target.Event += h and h => target.Event -= h
  2. Check that reflection-obtained add accessor MethodInfo is non-null and create the delegate successfully
  3. Fix the wrapper/helper that forwarded a null addHandler

Example fix

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

Strategy: validation

Validate before calling

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

Type guard

static bool HasEventHandlers(Action<EventHandler> add, Action<EventHandler> remove) => add is not null && remove is not null;

Try / catch

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

Prevention

When it happens

Trigger: Calling FromEventPattern(Action<EventHandler>, Action<EventHandler>) with a null first argument, e.g. forwarding reflection-resolved add accessors that were not found.

Common situations: EventInfo.GetAddMethod() returning null and being passed along; storing the delegates in fields that were never initialized; generic event-wrapping helpers forwarding caller-supplied nulls.

Related errors


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

Appendix: source

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

        /// The current <see cref="SynchronizationContext"/> is captured during the call to FromEventPattern, and is used to post add and remove handler invocations.
        /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks.
        /// </para>
        /// <para>
        /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread
        /// 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>

View on GitHub (pinned to 94b5d5ab91)