dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'durationSelector')

Error message

Value cannot be null. (Parameter 'durationSelector')

What it means

GroupByUntil needs a durationSelector that maps each created group to an observable signaling when the group closes. In the (source, keySelector, durationSelector, comparer) overload, a null durationSelector throws ArgumentNullException immediately at call time. The check exists so group lifetime logic is always present before the pipeline is built.

Solutions

  1. Supply a duration selector, e.g. g => AsyncObservable.Never<TDuration>() for never-closing groups
  2. Verify the expression producing the duration selector is not null at the call site
  3. Use plain GroupBy instead if group lifetime should be unbounded

Example fix

// before
var groups = source.GroupByUntil(x => x.Key, null, comparer);
// after
var groups = source.GroupByUntil(x => x.Key, g => AsyncObservable.Never<Unit>(), comparer);
Defensive patterns

Strategy: validation

Validate before calling

if (durationSelector == null) throw new ArgumentException("durationSelector is required by GroupByUntil; use AsyncObservable.Never<T>() for unbounded groups");

Type guard

bool HasDurationSelector<TSource,TKey,TD>(Func<IAsyncGroupedObservable<TKey,TSource>,IAsyncObservable<TD>> f) => f is not null;

Try / catch

try { var groups = source.GroupByUntil(ks, ds, cmp); } catch (ArgumentNullException ex) when (ex.ParamName == "durationSelector") { var groups = source.GroupBy(ks, cmp); }

Prevention

When it happens

Trigger: Calling AsyncObservable.GroupByUntil(source, keySelector, null, comparer) with the comparer overload, omitting the Func<IGroupedAsyncObservable<TKey,TSource>, IAsyncObservable<TDuration>> argument.

Common situations: Porting from Rx.NET GroupBy (which has no duration selector) and dropping the argument, building the delegate via reflection/config that resolved to null, or passing a method group that failed to bind.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs:38

            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));
            if (durationSelector == null)
                throw new ArgumentNullException(nameof(durationSelector));

            return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TSource>>.From(
                source,
                (keySelector, durationSelector),
                static (source, state, observer) => GroupByUntilCore<TSource, TKey, TSource, TDuration>(source, observer, (o, d) => AsyncObserver.GroupByUntil(o, d, state.keySelector, state.durationSelector)));
        }

        public static IAsyncObservable<IGroupedAsyncObservable<TKey, TSource>> GroupByUntil<TSource, TKey, TDuration>(this IAsyncObservable<TSource> source, Func<TSource, TKey> keySelector, Func<IGroupedAsyncObservable<TKey, TSource>, IAsyncObservable<TDuration>> durationSelector, IEqualityComparer<TKey> comparer)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));
            if (durationSelector == null)
                throw new ArgumentNullException(nameof(durationSelector));
            if (comparer == null)
                throw new ArgumentNullException(nameof(comparer));

            return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TSource>>.From(
                source,
                (keySelector, durationSelector, comparer),
                static (source, state, observer) => GroupByUntilCore<TSource, TKey, TSource, TDuration>(source, observer, (o, d) => AsyncObserver.GroupByUntil(o, d, state.keySelector, state.durationSelector, state.comparer)));
        }

        public static IAsyncObservable<IGroupedAsyncObservable<TKey, TSource>> GroupByUntil<TSource, TKey, TDuration>(this IAsyncObservable<TSource> source, Func<TSource, TKey> keySelector, Func<IGroupedAsyncObservable<TKey, TSource>, IAsyncObservable<TDuration>> durationSelector, int capacity)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));
            if (durationSelector == null)
                throw new ArgumentNullException(nameof(durationSelector));
            if (capacity < 0)

View on GitHub (pinned to 94b5d5ab91)