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

  1. Ensure the RepeatWhen/RetryWhen handler always returns a valid IObservable (e.g. Observable.Return(unit) or Observable.Empty<U>() for no redo).
  2. Guard inside the handler: if the computed stream is null, substitute Observable.Never or throw a descriptive exception yourself.
  3. 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

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


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)