dotnet/reactive · error · ArgumentNullException

ArgumentNullException: durationSelector

Error message

ArgumentNullException: durationSelector

What it means

System.Reactive.Async's GroupByUntil overload with a capacity parameter validates all delegate arguments before doing any work. The durationSelector delegate, which maps each grouped observable to a duration observable that controls when the group completes, was passed as null. The library throws ArgumentNullException eagerly so the failure surfaces at call time rather than silently breaking group lifetime logic later.

Solutions

  1. Pass a non-null duration selector, e.g. grp => grp.Take(1), that returns the duration observable for each group
  2. Check the variable intended as durationSelector for a null value before calling the operator
  3. If a default group lifetime is intended, use a simpler GroupBy overload or supply grp => Observable.Never<TDuration>() equivalent so groups stay open until the source completes

Example fix

// before
var res = source.GroupByUntil(x => x.Key, null, 16);
// after
var res = source.GroupByUntil(x => x.Key, grp => grp.TakeUntil(closeSignal), 16);
Defensive patterns

Strategy: validation

Validate before calling

if (durationSelector == null) throw new ArgumentException("durationSelector must be provided before calling GroupByUntil", nameof(durationSelector));

Type guard

bool IsValidDurationSelector<TSource,TKey,TDuration>(Func<IGroupedAsyncObservable<TKey,TSource>, IAsyncObservable<TDuration>>? f) => f is not null;

Try / catch

try { var res = source.GroupByUntil(keySelector, durationSelector, capacity); }
catch (ArgumentNullException ex) when (ex.ParamName == "durationSelector") { /* supply a default duration selector and retry */ }

Prevention

When it happens

Trigger: Calling the GroupByUntil(observer, subscription, keySelector, durationSelector, capacity) overload (GroupByUntil.cs:349) with a null durationSelector argument, typically because the duration lambda was left out or a nullable variable holding the delegate was never assigned.

Common situations: Building a query conditionally where the duration selector is only sometimes assigned; refactoring that removed the duration lambda; passing a method group that resolves to null via a factory; calling GroupByUntil dynamically through reflection or DI where arguments are constructed at runtime.

Related errors


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

Appendix: source

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

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

            return GroupByUntil(observer, subscription, keySelector, durationSelector, int.MaxValue, comparer);
        }

        public static ValueTask<(IAsyncObserver<TSource>, IAsyncDisposable)> GroupByUntil<TSource, TKey, TDuration>(IAsyncObserver<IGroupedAsyncObservable<TKey, TSource>> observer, IAsyncDisposable subscription, Func<TSource, TKey> keySelector, Func<IGroupedAsyncObservable<TKey, TSource>, IAsyncObservable<TDuration>> durationSelector, int capacity)
        {
            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 (durationSelector == null)
                throw new ArgumentNullException(nameof(durationSelector));
            if (capacity < 0)
                throw new ArgumentOutOfRangeException(nameof(capacity));

            return GroupByUntil(observer, subscription, keySelector, durationSelector, capacity, EqualityComparer<TKey>.Default);
        }

        public static ValueTask<(IAsyncObserver<TSource>, IAsyncDisposable)> GroupByUntil<TSource, TKey, TDuration>(IAsyncObserver<IGroupedAsyncObservable<TKey, TSource>> observer, IAsyncDisposable subscription, Func<TSource, TKey> keySelector, Func<IGroupedAsyncObservable<TKey, TSource>, IAsyncObservable<TDuration>> durationSelector, int capacity, 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 (durationSelector == null)
                throw new ArgumentNullException(nameof(durationSelector));
            if (capacity < 0)
                throw new ArgumentOutOfRangeException(nameof(capacity));

View on GitHub (pinned to 94b5d5ab91)