{"record":{"id":"b9e760ee9e2e6b5d","repo":"dotnet/reactive","slug":"value-cannot-be-null-parameter-addhandler","errorCode":null,"errorMessage":"Value cannot be null. (Parameter 'addHandler')","messagePattern":"Value cannot be null\\. \\(Parameter 'addHandler'\\)","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"Rx.NET/Source/src/System.Reactive/Linq/Observable.Events.cs","lineNumber":52,"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<object>> FromEventPattern(Action<EventHandler> addHandler, Action<EventHandler> removeHandler)\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(addHandler, removeHandler);\n        }\n\n        /// <summary>\n        /// Converts a .NET event, conforming to the standard .NET event pattern based on <see cref=\"EventHandler\"/>, to an observable sequence.\n        /// Each event invocation is surfaced through an OnNext message in the resulting sequence.\n        /// For conversion of events that don't conform to the standard .NET event pattern, use any of the FromEvent overloads instead.\n        /// </summary>\n        /// <param name=\"addHandler\">Action that attaches the given event handler to the underlying .NET event.</param>\n        /// <param name=\"removeHandler\">Action that detaches the given event handler from the underlying .NET event.</param>\n        /// <param name=\"scheduler\">The scheduler to run the add and remove event handler logic on.</param>","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/Rx.NET/Source/src/System.Reactive/Linq/Observable.Events.cs#L34-L70","documentation":"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.","triggerScenarios":"Calling FromEventPattern(Action<EventHandler>, Action<EventHandler>) with a null first argument, e.g. forwarding reflection-resolved add accessors that were not found.","commonSituations":"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.","solutions":["Pass non-null lambdas such as h => target.Event += h and h => target.Event -= h","Check that reflection-obtained add accessor MethodInfo is non-null and create the delegate successfully","Fix the wrapper/helper that forwarded a null addHandler"],"exampleFix":"// before\nvar add = (Action<EventHandler>)null;\nObservable.FromEventPattern(add, h => target.Click -= h);\n// after\nObservable.FromEventPattern(h => target.Click += h, h => target.Click -= h);","handlingStrategy":"validation","validationCode":"if (addHandler == null) throw new ArgumentNullException(nameof(addHandler));\nif (removeHandler == null) throw new ArgumentNullException(nameof(removeHandler));\nvar seq = Observable.FromEventPattern(addHandler, removeHandler);","typeGuard":"static bool HasEventHandlers(Action<EventHandler> add, Action<EventHandler> remove) => add is not null && remove is not null;","tryCatchPattern":"try\n{\n    var seq = Observable.FromEventPattern(addHandler, removeHandler);\n}\ncatch (ArgumentNullException ex) when (ex.ParamName == \"addHandler\")\n{\n    throw new InvalidOperationException(\"Event add accessor was not resolved\", ex);\n}","preventionTips":["Inline the h => target.Event += h / h => target.Event -= h lambdas at the call site","When using reflection, assert EventInfo.GetAddMethod() and GetRemoveMethod() are non-null","Prefer typed FromEventPattern overloads (target, nameof(event)) to avoid manual delegates"],"tags":["rx","csharp","null-argument","events"],"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"}