{"record":{"id":"9979603163b397bb","repo":"dotnet/reactive","slug":"value-cannot-be-null-parameter-conversion","errorCode":null,"errorMessage":"Value cannot be null. (Parameter 'conversion')","messagePattern":"Value cannot be null\\. \\(Parameter 'conversion'\\)","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"Rx.NET/Source/src/System.Reactive/Linq/Observable.Events.cs","lineNumber":243,"sourceCode":"        /// The current <see cref=\"SynchronizationContext\"/> is captured during the call to FromEventPattern, and is used to post add and remove handler invocations.\n        /// This behavior ensures add and remove handler operations for thread-affine events are accessed from the same context, as required by some UI frameworks.\n        /// </para>\n        /// <para>\n        /// If no SynchronizationContext is present at the point of calling FromEventPattern, add and remove handler invocations are made synchronously on the thread\n        /// making the Subscribe or Dispose call, respectively.\n        /// </para>\n        /// <para>\n        /// It's recommended to lift FromEventPattern calls outside event stream query expressions due to the free-threaded nature of Reactive Extensions. Doing so\n        /// makes the captured SynchronizationContext predictable. This best practice also reduces clutter of bridging code inside queries, making the query expressions\n        /// more concise and easier to understand.\n        /// </para>\n        /// </remarks>\n        /// <seealso cref=\"ToEventPattern\"/>\n        public static IObservable<EventPattern<TEventArgs>> FromEventPattern<TDelegate, TEventArgs>(Func<EventHandler<TEventArgs>, TDelegate> conversion, Action<TDelegate> addHandler, Action<TDelegate> removeHandler)\n        {\n            if (conversion == null)\n            {\n                throw new ArgumentNullException(nameof(conversion));\n            }\n\n            if (addHandler == null)\n            {\n                throw new ArgumentNullException(nameof(addHandler));\n            }\n\n            if (removeHandler == null)\n            {\n                throw new ArgumentNullException(nameof(removeHandler));\n            }\n\n            return s_impl.FromEventPattern(conversion, addHandler, removeHandler);\n        }\n\n        /// <summary>\n        /// Converts a .NET event, conforming to the standard .NET event pattern based on <see cref=\"EventHandler{TEventArgs}\"/>, to an observable sequence.\n        /// Each event invocation is surfaced through an OnNext message in the resulting sequence.","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/Rx.NET/Source/src/System.Reactive/Linq/Observable.Events.cs#L225-L261","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nFunc<EventHandler<EventArgs>, CustomHandler> conv = null;\nvar xs = Observable.FromEventPattern<CustomHandler, EventArgs>(conv, add, remove);\n// after\nvar xs = Observable.FromEventPattern<CustomHandler, EventArgs>(h => new CustomHandler(h.Invoke), add, remove);","handlingStrategy":"validation","validationCode":"if (conversion is null) throw new ArgumentNullException(nameof(conversion));\n// quick smoke test of the converter:\nvar probe = conversion(h => h(null!, EventArgs.Empty));","typeGuard":"static bool IsValidConversionArgs<TDelegate, TEventArgs>(Func<EventHandler<TEventArgs>, TDelegate> conv, Action<TDelegate> add, Action<TDelegate> remove) => conv != null && add != null && remove != null;","tryCatchPattern":"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); }","preventionTips":["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"],"tags":["csharp","null-argument","reactive-extensions","delegates"],"backgroundTag":"null-argument","analyzedSha":"94b5d5ab912789f5abe9a72138a25bbd716fe59c","analyzedAt":"2026-09-15T02:26:24.759Z","contentChangedAt":"2026-09-15T02:26:24.759Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}