dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'observer')

Error message

Value cannot be null. (Parameter 'observer')

What it means

AsyncObserver.GroupBy is the internal factory that builds the observer tuple driving the GroupBy pipeline. It validates observer first and throws ArgumentNullException when it is null. This overload is normally invoked by the public GroupBy operator and by GroupByCore, so hitting it usually means an observer pipeline was assembled manually with a missing observer.

Solutions

  1. Pass the real IAsyncObserver instance obtained from CreateAsyncObservable.From's callback; do not construct the observer pipeline with null.
  2. If building a custom operator, ensure your observerFactory returns a valid observer before calling AsyncObserver.GroupBy.
  3. Guard with a null check on the observer before invoking the factory.

Example fix

// before
return AsyncObserver.GroupBy(null, subscription, keySelector);
// after
if (observer == null) throw new ArgumentNullException(nameof(observer));
return AsyncObserver.GroupBy(observer, subscription, keySelector);
Defensive patterns

Strategy: validation

Validate before calling

if (observer == null) throw new ArgumentNullException(nameof(observer));
var (o, d) = AsyncObserver.GroupBy(observer, subscription, keySelector);

Type guard

bool CanBuildGroupBy<TSource, TKey>(IAsyncObserver<IGroupedAsyncObservable<TKey, TSource>>? observer) => observer is not null;

Try / catch

try
{
    var (o, d) = AsyncObserver.GroupBy(observer, subscription, keySelector);
}
catch (ArgumentNullException ex) when (ex.ParamName == "observer")
{
    throw new InvalidOperationException("GroupBy requires a non-null observer from the operator callback.", ex);
}

Prevention

When it happens

Trigger: Calling AsyncObserver.GroupBy<TSource, TKey>(observer, subscription, keySelector) directly (or via a custom operator builder) with a null IAsyncObserver<IGroupedAsyncObservable<TKey, TSource>>.

Common situations: Hand-rolling custom async operators on top of AsyncObserver.*; a CreateAsyncObservable callback receiving and forwarding a null observer; wiring the observer tuple in the wrong order so null lands in the observer slot.

Related errors


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

Appendix: source

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

        private static async ValueTask<IAsyncDisposable> GroupByCore<TSource, TKey, TElement>(IAsyncObservable<TSource> source, IAsyncObserver<IGroupedAsyncObservable<TKey, TElement>> observer, Func<IAsyncObserver<IGroupedAsyncObservable<TKey, TElement>>, IAsyncDisposable, (IAsyncObserver<TSource>, IAsyncDisposable)> createObserver)
        {
            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));

View on GitHub (pinned to 94b5d5ab91)