dotnet/reactive · error · NullReferenceException
The handler returned a null IObservable
Error message
The handler returned a null IObservable
What it means
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).
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.
Example fix
// before source.RepeatWhen(completions => completions.SelectMany(c => lookup["redo"])); // lookup may miss // after source.RepeatWhen(completions => completions.SelectMany(c => lookup["redo"] ?? Observable.Return(Unit.Default)));
Defensive patterns
Strategy: validation
Validate before calling
// C# - validate handler output before subscribing IObservable<Unit> redo = handler(completions); if (redo is null) redo = Observable.Return(Unit.Default); source.RepeatWhen(_ => redo).Subscribe(...);
Type guard
// C# static bool IsValidRedo<U>(IObservable<U> o) => o is not null;
Try / catch
source.RepeatWhen(completions => handler(completions)).Subscribe(
onNext,
ex => { if (ex is NullReferenceException && ex.Message.Contains("null IObservable")) FixHandler(); else throw ex; }); Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- The handler returned a null IObservable
- Value cannot be null. (Parameter 'source')
- ArgumentNullException
- nameof(source)
- nameof(source)
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/a91ba3b6d131c27d.
Report an issue: GitHub.
Appendix: source
Thrown at Rx.NET/Source/src/System.Reactive/Linq/Observable/RepeatWhen.cs:40
public IDisposable Subscribe(IObserver<T> observer)
{
if (observer == null)
{
throw new ArgumentNullException(nameof(observer));
}
var completeSignals = new Subject<object>();
IObservable<U> redo;
try
{
redo = _handler(completeSignals);
if (redo == null)
{
#pragma warning disable CA2201 // (Do not raise reserved exception types.) Backwards compatibility prevents us from complying.
throw new NullReferenceException("The handler returned a null IObservable");
#pragma warning restore CA2201
}
}
catch (Exception ex)
{
observer.OnError(ex);
return Disposable.Empty;
}
var parent = new MainObserver(observer, _source, new RedoSerializedObserver<object>(completeSignals));
var d = redo.SubscribeSafe(parent.HandlerConsumer);
parent._handlerUpstream.Disposable = d;
parent.HandlerNext();
return parent;
}View on GitHub (pinned to 94b5d5ab91)