dotnet/reactive · error · ArgumentNullException
subscriptions
Error message
subscriptions
What it means
AsyncObserver.GroupJoin throws ArgumentNullException when the subscriptions composite disposable is null. The subscriptions container tracks all duration-observable subscriptions so they can be disposed when either source completes; a null value would leak or crash the join logic. This is an internal invariant enforced by a fail-fast guard.
Solutions
- Construct a CompositeAsyncDisposable and pass it as the subscriptions argument.
- Follow the pattern in GroupJoin.cs: create `var subscriptions = new CompositeAsyncDisposable();` before calling AsyncObserver.GroupJoin.
- If wrapping, never forward a null disposable — throw your own descriptive exception earlier.
Example fix
// before var (l, r, d) = AsyncObserver.GroupJoin(observer, null, ld, rd, rs); // after var subscriptions = new CompositeAsyncDisposable(); var (l, r, d) = AsyncObserver.GroupJoin(observer, subscriptions, ld, rd, rs);
Defensive patterns
Strategy: validation
Validate before calling
if (subscriptions == null) subscriptions = new CompositeAsyncDisposable();
Type guard
bool HasSubscriptions(IAsyncDisposable d) => d is not null;
Try / catch
try { var parts = AsyncObserver.GroupJoin(observer, subs, ld, rd, rs); }
catch (ArgumentNullException ex) when (ex.ParamName == "subscriptions") { /* construct CompositeAsyncDisposable and retry */ } Prevention
- Always create the CompositeAsyncDisposable before calling AsyncObserver.GroupJoin
- Follow the exact pattern in GroupJoin.cs's public operator
- Treat disposables as required constructor dependencies, never optional
When it happens
Trigger: Calling AsyncObserver.GroupJoin with null as the second (IAsyncDisposable subscriptions) argument — typically from custom operator code that forgot to construct the CompositeAsyncDisposable before delegating.
Common situations: Hand-rolled GroupJoin-like operators; unit tests calling the factory directly; code refactors where disposable creation moved after the factory call.
Related errors
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/b9ef3474540633e5.
Report an issue: GitHub.
Appendix: source
Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupJoin.cs:52
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);
var rightId = default(int);
View on GitHub (pinned to 94b5d5ab91)