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
- Supply a converter, e.g. h => (CustomEventHandler)h.Invoke, that wraps an EventHandler<TEventArgs> as the target delegate type
- Ensure TDelegate is explicitly specified so overload resolution picks the conversion overload
- 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
- For events with custom delegate types, write the converter lambda inline (h => new CustomHandler(h.Invoke)) instead of a variable
- Specify both generic arguments explicitly to keep overload resolution deterministic
- If the event is a plain EventHandler<TEventArgs>, avoid the conversion overload entirely
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
- Value cannot be null. (Parameter 'selector')
- Value cannot be null. (Parameter 'source8')
- iteratorMethod
- source
- selector
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)