dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'subscription')

Error message

Value cannot be null. (Parameter 'subscription')

What it means

AsyncObserver.GroupBy requires the IAsyncDisposable representing the upstream subscription so groups can be disposed when the pipeline terminates; a null subscription is rejected with ArgumentNullException. This guard sits in the simplest observer-level overload (observer, subscription, keySelector).

Solutions

  1. Pass the subscription IAsyncDisposable supplied by the CreateAsyncObservable callback.
  2. If no disposal is needed, provide a no-op disposable (e.g. AsyncDisposable.Nop / DefaultAsyncDisposable) rather than null.
  3. Add a null guard with a clear message in the custom operator before delegating.

Example fix

// before
return AsyncObserver.GroupBy(observer, null, keySelector);
// after
return AsyncObserver.GroupBy(observer, subscription ?? AsyncDisposable.Nop, keySelector);
Defensive patterns

Strategy: validation

Validate before calling

subscription ??= AsyncDisposable.Nop;
var (o, d) = AsyncObserver.GroupBy(observer, subscription, keySelector);

Type guard

bool HasSubscription(IAsyncDisposable? s) => s is not null;

Try / catch

try
{
    var (o, d) = AsyncObserver.GroupBy(observer, subscription, keySelector);
}
catch (ArgumentNullException ex) when (ex.ParamName == "subscription")
{
    var (o, d) = AsyncObserver.GroupBy(observer, AsyncDisposable.Nop, keySelector);
}

Prevention

When it happens

Trigger: Calling AsyncObserver.GroupBy(observer, subscription, keySelector) with subscription == null, typically when wiring the observer tuple manually.

Common situations: Custom operator code forgets to thread the subscription disposable through; a helper caches the observer but not the subscription; refactor dropped the subscription argument.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupBy.cs:290

            var d = new SingleAssignmentAsyncDisposable();

            var (sink, subscription) = createObserver(observer, d);

            var inner = await source.SubscribeSafeAsync(sink).ConfigureAwait(false);
            await d.AssignAsync(inner).ConfigureAwait(false);

            return subscription;
        }
    }

    public partial class AsyncObserver
    {
        public static (IAsyncObserver<TSource>, IAsyncDisposable) GroupBy<TSource, TKey>(IAsyncObserver<IGroupedAsyncObservable<TKey, TSource>> observer, IAsyncDisposable subscription, Func<TSource, TKey> keySelector)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));
            if (subscription == null)
                throw new ArgumentNullException(nameof(subscription));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));

            return GroupBy(observer, subscription, keySelector, int.MaxValue, EqualityComparer<TKey>.Default);
        }

        public static (IAsyncObserver<TSource>, IAsyncDisposable) GroupBy<TSource, TKey>(IAsyncObserver<IGroupedAsyncObservable<TKey, TSource>> observer, IAsyncDisposable subscription, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));
            if (subscription == null)
                throw new ArgumentNullException(nameof(subscription));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));
            if (comparer == null)
                throw new ArgumentNullException(nameof(comparer));

            return GroupBy(observer, subscription, keySelector, int.MaxValue, comparer);

View on GitHub (pinned to 94b5d5ab91)