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

  1. 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).
  2. Guard the delegate before calling: if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector)); or substitute a default.
  3. 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

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


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)