dotnet/reactive · error · ArgumentNullException

observer

Error message

observer

What it means

The internal AsyncObserver.Join factory throws ArgumentNullException because the downstream IAsyncObserver<TResult> was null. This factory builds the paired left/right observers used by the Join operator; a null observer means results would have nowhere to go. The guard exists so the internal wiring always has a valid downstream sink.

Solutions

  1. Pass the actual downstream IAsyncObserver<TResult> instance as the first argument.
  2. In custom operator code, ensure the observer received in your subscription lambda is forwarded, not discarded.
  3. If you need a no-op sink for tests, provide a stub observer implementation rather than null.

Example fix

// before
var (lo, ro, disp) = AsyncObserver.Join(null, subs, lds, rds, rs);
// after
var (lo, ro, disp) = AsyncObserver.Join(downstreamObserver, subs, lds, rds, rs);
Defensive patterns

Strategy: validation

Validate before calling

if (observer is null) throw new ArgumentException("downstream observer is required", nameof(observer));

Type guard

bool ObserverReady<T>(IAsyncObserver<T>? o) => o is not null;

Try / catch

try { var parts = AsyncObserver.Join(observer, subs, lds, rds, rs); }
catch (ArgumentNullException ex) when (ex.ParamName == "observer") { /* fix custom plumbing to forward downstream observer */ }

Prevention

When it happens

Trigger: Calling AsyncObserver.Join(observer: null, subscriptions, leftDurationSelector, rightDurationSelector, resultSelector) — normally only reachable via custom operator plumbing that passes a null downstream observer.

Common situations: Custom operator implementations or test harnesses that construct AsyncObserver.Join directly and forget to supply the downstream observer, often because an outer observer variable was never initialized.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/b4330993d815356f. Report an issue: GitHub.

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/Join.cs:52

                    var (leftObserver, rightObserver, disposable) = AsyncObserver.Join(observer, subscriptions, state.leftDurationSelector, state.rightDurationSelector, state.resultSelector);

                    var leftSubscription = await left.SubscribeSafeAsync(leftObserver).ConfigureAwait(false);
                    await subscriptions.AddAsync(leftSubscription).ConfigureAwait(false);

                    var rightSubscription = await state.right.SubscribeSafeAsync(rightObserver).ConfigureAwait(false);
                    await subscriptions.AddAsync(rightSubscription).ConfigureAwait(false);

                    return disposable;
                });
        }
    }

    public partial class AsyncObserver
    {
        public static (IAsyncObserver<TLeft>, IAsyncObserver<TRight>, IAsyncDisposable) Join<TLeft, TRight, TLeftDuration, TRightDuration, TResult>(IAsyncObserver<TResult> observer, IAsyncDisposable subscriptions, Func<TLeft, IAsyncObservable<TLeftDuration>> leftDurationSelector, Func<TRight, IAsyncObservable<TRightDuration>> rightDurationSelector, Func<TLeft, TRight, TResult> resultSelector)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));
            if (subscriptions == null)
                throw new ArgumentNullException(nameof(subscriptions));
            if (leftDurationSelector == null)
                throw new ArgumentNullException(nameof(leftDurationSelector));
            if (rightDurationSelector == null)
                throw new ArgumentNullException(nameof(rightDurationSelector));
            if (resultSelector == null)
                throw new ArgumentNullException(nameof(resultSelector));

            var gate = new AsyncGate();

            var group = new CompositeAsyncDisposable(subscriptions);

            var leftMap = new SortedDictionary<int, TLeft>();
            var rightMap = new SortedDictionary<int, TRight>();

            var leftDone = false;
            var rightDone = false;

View on GitHub (pinned to 94b5d5ab91)