dotnet/reactive · error · ArgumentNullException
elementSelector
Error message
elementSelector
What it means
GroupByUntil validates all delegate arguments up-front and throws ArgumentNullException when elementSelector is null. The elementSelector (Func<TSource, ValueTask<TElement>>) is required because it projects each source item into the element exposed on the emitted IGroupedAsyncObservable groups. The library fails fast at subscribe-plan time rather than failing later inside the async pipeline.
Solutions
- Pass a non-null elementSelector; if no projection is needed, use GroupBy (key only) or pass an identity selector such as static x => new ValueTask<TElement>(x).
- Guard the delegate before calling: if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector)); or substitute a default.
- Check the overload you intend — some overloads require both keySelector and elementSelector; pick the one matching your available delegates.
Example fix
// before var groups = source.GroupByUntil(k => k.Id, null, g => g.Where(x => x.Stale).Take(1)); // after var groups = source.GroupByUntil(k => k.Id, x => new ValueTask<TElement>(x), g => g.Where(x => x.Stale).Take(1));
Defensive patterns
Strategy: validation
Validate before calling
if (keySelector == null) throw new ArgumentNullException(nameof(keySelector)); if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector)); if (durationSelector == null) throw new ArgumentNullException(nameof(durationSelector));
Type guard
bool IsValidSelector<TIn, TOut>(Func<TIn, ValueTask<TOut>> f) => f is not null;
Try / catch
try { var groups = source.GroupByUntil(key, elem, dur); }
catch (ArgumentNullException ex) { logger.LogError(ex, "GroupByUntil delegate missing: {Param}", ex.ParamName); } Prevention
- Use static lambdas so the compiler forces all arguments to be assigned.
- Never default delegate parameters to null in wrapper APIs.
- Prefer the simpler GroupBy operator when no element projection is needed.
When it happens
Trigger: Calling any GroupByUntil overload that accepts an elementSelector while passing null for it, e.g. source.GroupByUntil(key, null, durationSelector) or the comparer/capacity variants, in GroupByUntil.cs:235.
Common situations: Conditionally-built query pipelines where a projection variable is only assigned on one branch; refactors that removed an element projection but left the call shape; interop code that maps optional delegates from nullable inputs.
Related errors
- Value cannot be null. (Parameter 'keySelector')
- Value cannot be null. (Parameter 'durationSelector')
- Value cannot be null. (Parameter 'comparer')
- ArgumentNullException: durationSelector
- ArgumentNullException: observer
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/b168ad7f99434834.
Report an issue: GitHub.
Appendix: source
Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs:235
if (capacity < 0)
throw new ArgumentOutOfRangeException(nameof(capacity));
if (comparer == null)
throw new ArgumentNullException(nameof(comparer));
return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TSource>>.From(
source,
(keySelector, durationSelector, capacity, comparer),
static (source, state, observer) => GroupByUntilCore<TSource, TKey, TSource, TDuration>(source, observer, (o, d) => AsyncObserver.GroupByUntil(o, d, state.keySelector, state.durationSelector, state.capacity, state.comparer)));
}
public static IAsyncObservable<IGroupedAsyncObservable<TKey, TElement>> GroupByUntil<TSource, TKey, TElement, TDuration>(this IAsyncObservable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<TSource, ValueTask<TElement>> elementSelector, Func<IGroupedAsyncObservable<TKey, TElement>, IAsyncObservable<TDuration>> durationSelector)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
if (elementSelector == null)
throw new ArgumentNullException(nameof(elementSelector));
if (durationSelector == null)
throw new ArgumentNullException(nameof(durationSelector));
return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TElement>>.From(
source,
(keySelector, elementSelector, durationSelector),
static (source, state, observer) => GroupByUntilCore<TSource, TKey, TElement, TDuration>(source, observer, (o, d) => AsyncObserver.GroupByUntil(o, d, state.keySelector, state.elementSelector, state.durationSelector)));
}
public static IAsyncObservable<IGroupedAsyncObservable<TKey, TElement>> GroupByUntil<TSource, TKey, TElement, TDuration>(this IAsyncObservable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<TSource, ValueTask<TElement>> elementSelector, Func<IGroupedAsyncObservable<TKey, TElement>, IAsyncObservable<TDuration>> durationSelector, IEqualityComparer<TKey> comparer)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));
if (elementSelector == null)
throw new ArgumentNullException(nameof(elementSelector));
if (durationSelector == null)View on GitHub (pinned to 94b5d5ab91)