dotnet/reactive · error · ArgumentNullException

observer

Error message

observer

What it means

The internal AsyncObserver.GroupJoin factory validates its IAsyncObserver<TResult> observer argument and throws ArgumentNullException when it is null. The observer is the downstream sink that receives the joined results, so without it the operator has nothing to notify. This guard is hit when the public GroupJoin operator wires up Create<TResult> or when calling this factory directly.

Solutions

  1. Ensure the observer passed to AsyncObserver.GroupJoin is the non-null one received from the Create/IAsyncObservable subscription callback.
  2. If writing a custom operator, pass the observer parameter straight through rather than a captured variable that may be null.
  3. Add a null check on your own plumbing before delegating to AsyncObserver.GroupJoin.

Example fix

// before
return Create<TResult>(async observer => { var (l, r, d) = AsyncObserver.GroupJoin(null, subs, ld, rd, rs); ... });
// after
return Create<TResult>(async observer => { var (l, r, d) = AsyncObserver.GroupJoin(observer, subs, ld, rd, rs); ... });
Defensive patterns

Strategy: validation

Validate before calling

if (observer == null) throw new ArgumentNullException(nameof(observer));

Type guard

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

Try / catch

try { var parts = AsyncObserver.GroupJoin(observer, subs, ld, rd, rs); }
catch (ArgumentNullException ex) when (ex.ParamName == "observer") { /* fix operator plumbing */ }

Prevention

When it happens

Trigger: Calling AsyncObserver.GroupJoin with null as the first argument; the public operator would only hit this if its Create callback supplied a null observer, which normally indicates a bug in custom operator plumbing rather than user code.

Common situations: Custom operator authoring that forgets to thread the observer through; tests invoking AsyncObserver.GroupJoin directly with placeholder nulls; refactors of the Create pipeline.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupJoin.cs:50

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

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

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

                return disposable;
            });
        }
    }

    public partial class AsyncObserver
    {
        public static (IAsyncObserver<TLeft>, IAsyncObserver<TRight>, IAsyncDisposable) GroupJoin<TLeft, TRight, TLeftDuration, TRightDuration, TResult>(IAsyncObserver<TResult> observer, IAsyncDisposable subscriptions, Func<TLeft, IAsyncObservable<TLeftDuration>> leftDurationSelector, Func<TRight, IAsyncObservable<TRightDuration>> rightDurationSelector, Func<TLeft, IAsyncObservable<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 refCount = new RefCountAsyncDisposable(group);

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

            var leftId = default(int);

View on GitHub (pinned to 94b5d5ab91)