{"record":{"id":"a91ba3b6d131c27d","repo":"dotnet/reactive","slug":"the-handler-returned-a-null-iobservable","errorCode":null,"errorMessage":"The handler returned a null IObservable","messagePattern":"The handler returned a null IObservable","errorType":"exception","errorClass":"NullReferenceException","httpStatus":null,"severity":"error","filePath":"Rx.NET/Source/src/System.Reactive/Linq/Observable/RepeatWhen.cs","lineNumber":40,"sourceCode":"        public IDisposable Subscribe(IObserver<T> observer)\n        {\n            if (observer == null)\n            {\n                throw new ArgumentNullException(nameof(observer));\n            }\n\n            var completeSignals = new Subject<object>();\n\n            IObservable<U> redo;\n\n            try\n            {\n                redo = _handler(completeSignals);\n                \n                if (redo == null)\n                {\n#pragma warning disable CA2201 // (Do not raise reserved exception types.) Backwards compatibility prevents us from complying.\n                    throw new NullReferenceException(\"The handler returned a null IObservable\");\n#pragma warning restore CA2201\n                }\n            }\n            catch (Exception ex)\n            {\n                observer.OnError(ex);\n                return Disposable.Empty;\n            }\n\n            var parent = new MainObserver(observer, _source, new RedoSerializedObserver<object>(completeSignals));\n\n            var d = redo.SubscribeSafe(parent.HandlerConsumer);\n            parent._handlerUpstream.Disposable = d;\n\n            parent.HandlerNext();\n\n            return parent;\n        }","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/Rx.NET/Source/src/System.Reactive/Linq/Observable/RepeatWhen.cs#L22-L58","documentation":"RepeatWhen invokes the user-supplied handler with a subject of completion signals and expects a non-null IObservable<U> governing when to resubscribe. If the handler returns null, the operator surfaces a NullReferenceException(\"The handler returned a null IObservable\") to the observer via OnError instead of failing mid-subscribe. The NullReferenceException type is kept for backwards compatibility (CA2201 suppressed).","triggerScenarios":"source.RepeatWhen(completeSignals => ...) where the lambda returns null, e.g. a lookup/dictionary of redo observables that misses, or a conditional that falls through without returning.","commonSituations":"Handlers built by mapping a config key to a retry stream where the key is absent; refactoring that changed a return path to implicitly return null; dynamic handler registration not yet populated.","solutions":["Ensure the RepeatWhen/RetryWhen handler always returns a valid IObservable (e.g. Observable.Return(unit) or Observable.Empty<U>() for no redo).","Guard inside the handler: if the computed stream is null, substitute Observable.Never or throw a descriptive exception yourself.","Handle the OnError in the subscription and log which handler produced the null."],"exampleFix":"// before\nsource.RepeatWhen(completions => completions.SelectMany(c => lookup[\"redo\"])); // lookup may miss\n// after\nsource.RepeatWhen(completions => completions.SelectMany(c => lookup[\"redo\"] ?? Observable.Return(Unit.Default)));","handlingStrategy":"validation","validationCode":"// C# - validate handler output before subscribing\nIObservable<Unit> redo = handler(completions);\nif (redo is null) redo = Observable.Return(Unit.Default);\nsource.RepeatWhen(_ => redo).Subscribe(...);","typeGuard":"// C#\nstatic bool IsValidRedo<U>(IObservable<U> o) => o is not null;","tryCatchPattern":"source.RepeatWhen(completions => handler(completions)).Subscribe(\n    onNext,\n    ex => { if (ex is NullReferenceException && ex.Message.Contains(\"null IObservable\")) FixHandler(); else throw ex; });","preventionTips":["Never return null from RepeatWhen/RetryWhen handlers; return Observable.Empty<U>() to mean 'do not redo'.","Test handlers for every branch, including dictionary misses.","Use TryGetValue with a default redo stream instead of indexing lookups."],"tags":["repeatwhen","null-handler-result","observable","invalid-handler"],"backgroundTag":"invalid-argument-value","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"}