dotnet/reactive · error · ArgumentNullException

removeHandler

Error message

removeHandler

What it means

FromEvent<TEventArgs>(addHandler, removeHandler) checks addHandler then removeHandler. This error is thrown when removeHandler is null (parameter name 'removeHandler'). removeHandler is essential for disposing the subscription and detaching the handler; the library refuses null to prevent event-handler leaks.

Solutions

  1. Pass h => source.Event -= h as removeHandler.
  2. Ensure the event has an accessible remove accessor (avoid add-only custom events).
  3. Implement add and remove symmetrically in wrappers.
  4. If unsubscribe is impossible, do not use FromEvent; adapt the source instead.

Example fix

// before
var obs = AsyncObservable.FromEvent<EventArgs>(h => obj.MyEvent += h, null);
// after
var obs = AsyncObservable.FromEvent<EventArgs>(h => obj.MyEvent += h, h => obj.MyEvent -= h);
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

try { var obs = AsyncObservable.FromEvent<EventArgs>(add, remove); } catch (ArgumentNullException ex) when (ex.ParamName == "removeHandler") { log.LogError(ex, "removeHandler required to detach on dispose"); }

Prevention

When it happens

Trigger: Calling AsyncObservable.FromEvent<TEventArgs>(addHandler, removeHandler) with removeHandler == null; FromEvent.cs:19 is the `throw new ArgumentNullException(nameof(removeHandler))`.

Common situations: Only the subscription side implemented in a custom event wrapper; events declared with add-only accessors; copied code where the unsubscribe lambda was deleted during refactoring.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/FromEvent.cs:19

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT License.
// See the LICENSE file in the project root for more information. 

using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;

namespace System.Reactive.Linq
{
    public partial class AsyncObservable
    {
        public static IAsyncObservable<TEventArgs> FromEvent<TEventArgs>(Action<Action<TEventArgs>> addHandler, Action<Action<TEventArgs>> removeHandler)
        {
            if (addHandler == null)
                throw new ArgumentNullException(nameof(addHandler));
            if (removeHandler == null)
                throw new ArgumentNullException(nameof(removeHandler));

            return FromEventCore<Action<TEventArgs>, TEventArgs>(h => h, addHandler, removeHandler, GetSchedulerForCurrentContext());
        }

        public static IAsyncObservable<TEventArgs> FromEvent<TEventArgs>(Action<Action<TEventArgs>> addHandler, Action<Action<TEventArgs>> 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 FromEventCore<Action<TEventArgs>, TEventArgs>(h => h, addHandler, removeHandler, scheduler);
        }

        public static IAsyncObservable<TEventArgs> FromEvent<TDelegate, TEventArgs>(Action<TDelegate> addHandler, Action<TDelegate> removeHandler)
        {

View on GitHub (pinned to 94b5d5ab91)