dotnet/reactive · error · ArgumentNullException

keySelector

Error message

keySelector

What it means

GroupBy throws ArgumentNullException because the keySelector Func<TSource, ValueTask<TKey>> is null. The key selector is the essence of grouping — without it no groups can be formed — so it is validated up front and the call aborts.

Solutions

  1. Supply a key selector lambda, e.g. x => x.CustomerId, wrapped in a ValueTask if needed.
  2. If the selector is optional, default to x => default before calling GroupBy.
  3. Null-check the delegate at the call site before entering the operator pipeline.
  4. Make sure the generic type arguments TKey match the selector's return type so the right overload is bound.

Example fix

// before
var grp = GroupBy<TSource, TKey>(observer, subscription, keySelector); // keySelector is null
// after
Func<TSource, ValueTask<TKey>> keySelector = async x => (await x.GetKeyAsync());
var grp = GroupBy<TSource, TKey>(observer, subscription, keySelector);
Defensive patterns

Strategy: validation

Validate before calling

if (keySelector == null) throw new InvalidOperationException("keySelector is required for GroupBy");
var grp = GroupBy<TSource, TKey>(observer, subscription, keySelector);

Type guard

bool IsValidSelector<TSource, TKey>(Func<TSource, ValueTask<TKey>> selector) => selector is not null;

Try / catch

try { var grp = GroupBy<TSource, TKey>(observer, subscription, keySelector); }
catch (ArgumentNullException ex) when (ex.ParamName == "keySelector") { /* supply a selector */ }

Prevention

When it happens

Trigger: Calling GroupBy<TSource,TKey>(observer, subscription, null) — a null keySelector literal or a Func variable that is null (e.g. an optional selector parameter not supplied).

Common situations: Passing a selector stored in a nullable delegate field that was never assigned; conditional query building where the selector expression was skipped; calling GroupBy from generic wrapper code that forwards a null selector.

Related errors


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

Appendix: source

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

                throw new ArgumentNullException(nameof(keySelector));
            if (elementSelector == null)
                throw new ArgumentNullException(nameof(elementSelector));
            if (capacity < 0)
                throw new ArgumentOutOfRangeException(nameof(capacity));
            if (comparer == null)
                throw new ArgumentNullException(nameof(comparer));

            return GroupBy<TSource, TKey, TElement>(observer, subscription, x => new ValueTask<TKey>(keySelector(x)), x => new ValueTask<TElement>(elementSelector(x)), capacity, comparer);
        }

        public static (IAsyncObserver<TSource>, IAsyncDisposable) GroupBy<TSource, TKey>(IAsyncObserver<IGroupedAsyncObservable<TKey, TSource>> observer, IAsyncDisposable subscription, Func<TSource, ValueTask<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, ValueTask<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)