dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'conversion')

Error message

Value cannot be null. (Parameter 'conversion')

What it means

Thrown by the conversion overload Observable.FromEventPattern<TDelegate,TEventArgs>(conversion, addHandler, removeHandler) when the conversion function is null. The conversion maps EventHandler<TEventArgs> to the custom delegate type so the CLR event can accept the Rx handler; without it the bridge cannot be built, so Rx rejects it first, before checking addHandler and removeHandler.

Solutions

  1. Supply a converter, e.g. h => (CustomEventHandler)h.Invoke, that wraps an EventHandler<TEventArgs> as the target delegate type
  2. Ensure TDelegate is explicitly specified so overload resolution picks the conversion overload
  3. If the event uses plain EventHandler<TEventArgs>, use the overload without a conversion parameter

Example fix

// before
Func<EventHandler<EventArgs>, CustomHandler> conv = null;
var xs = Observable.FromEventPattern<CustomHandler, EventArgs>(conv, add, remove);
// after
var xs = Observable.FromEventPattern<CustomHandler, EventArgs>(h => new CustomHandler(h.Invoke), add, remove);
Defensive patterns

Strategy: validation

Validate before calling

if (conversion is null) throw new ArgumentNullException(nameof(conversion));
// quick smoke test of the converter:
var probe = conversion(h => h(null!, EventArgs.Empty));

Type guard

static bool IsValidConversionArgs<TDelegate, TEventArgs>(Func<EventHandler<TEventArgs>, TDelegate> conv, Action<TDelegate> add, Action<TDelegate> remove) => conv != null && add != null && remove != null;

Try / catch

try { var xs = Observable.FromEventPattern<CustomHandler, EventArgs>(conversion, add, remove); } catch (ArgumentNullException ex) when (ex.ParamName == "conversion") { throw new InvalidOperationException("conversion delegate was null; supply h => new CustomHandler(h.Invoke)", ex); }

Prevention

When it happens

Trigger: Calling Observable.FromEventPattern<TDelegate, TEventArgs>(null, add, remove) — the Func<EventHandler<TEventArgs>, TDelegate> bridge is null, common when the custom delegate type is inferred oddly or the converter variable is unassigned.

Common situations: Events with custom (non-EventHandler) delegate types where the converter lambda was accidentally removed; passing null because the compiler could not infer TDelegate and the author gave up; refactoring dropped the converter.

Related errors


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

Appendix: source

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

        /// 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<TEventArgs>> FromEventPattern<TDelegate, TEventArgs>(Func<EventHandler<TEventArgs>, TDelegate> conversion, Action<TDelegate> addHandler, Action<TDelegate> removeHandler)
        {
            if (conversion == null)
            {
                throw new ArgumentNullException(nameof(conversion));
            }

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

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

            return s_impl.FromEventPattern(conversion, addHandler, removeHandler);
        }

        /// <summary>
        /// Converts a .NET event, conforming to the standard .NET event pattern based on <see cref="EventHandler{TEventArgs}"/>, to an observable sequence.
        /// Each event invocation is surfaced through an OnNext message in the resulting sequence.

View on GitHub (pinned to 94b5d5ab91)