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
- Supply a key selector lambda, e.g. x => x.CustomerId, wrapped in a ValueTask if needed.
- If the selector is optional, default to x => default before calling GroupBy.
- Null-check the delegate at the call site before entering the operator pipeline.
- 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
- Pass selectors as inline lambdas rather than nullable delegate fields when possible.
- Coalesce optional selectors: keySelector ?? (x => new ValueTask<TKey>(default)).
- Enable nullable reference types to catch uninitialized delegate fields.
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)