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
- Pass a non-null duration selector, e.g. grp => grp.Take(1), that returns the duration observable for each group
- Check the variable intended as durationSelector for a null value before calling the operator
- 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
- Never pass nullable delegate fields directly into Rx operators
- Prefer overload resolution sanity: list lambda arguments inline so you cannot omit the duration selector
- Write a unit test that constructs each query to catch unassigned delegates
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
- ArgumentNullException: observer
- ArgumentNullException: comparer
- Argument cannot be null (Parameter name: functionAsync)
- Argument cannot be null (Parameter name: scheduler)
- elementSelector
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)