dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'keySelector')

Error message

Value cannot be null. (Parameter 'keySelector')

What it means

GroupByUntil requires a keySelector delegate that maps each source element to a group key. The operator eagerly validates arguments when the overload with (source, keySelector, durationSelector, comparer) is called, and throws ArgumentNullException when keySelector is null. This fail-fast check prevents a null delegate from surfacing later inside the subscription pipeline.

Solutions

  1. Pass a non-null lambda as keySelector, e.g. x => x.Category
  2. Check the variable supplying the delegate for null before calling GroupByUntil
  3. If the key selector is optional by design, supply a default like _ => default(TKey) instead of null

Example fix

// before
Func<Order, string> keySel = config.Mode == "cat" ? (Func<Order,string>)null : o => o.Id;
var groups = source.GroupByUntil(keySel, g => Observable.Never<Unit>(), comparer);
// after
Func<Order, string> keySel = config.Mode == "cat" ? (Func<Order,string>)(o => o.Category) : o => o.Id;
var groups = source.GroupByUntil(keySel, g => Observable.Never<Unit>(), comparer);
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

bool HasKeySelector<TSource,TKey>(Func<TSource,TKey> f) => f is not null;

Try / catch

try { var groups = source.GroupByUntil(ks, ds, cmp); } catch (ArgumentNullException ex) when (ex.ParamName == "keySelector") { /* supply default selector or fail fast */ }

Prevention

When it happens

Trigger: Calling AsyncObservable.GroupByUntil(source, null, durationSelector, comparer) with the 4-argument overload that accepts an IEqualityComparer<TKey>, passing null for the keySelector parameter.

Common situations: Storing the key selector in a variable that was never assigned (e.g. a nullable Func field defaulting to null), conditional delegation where a lambda is only set in some code paths, or refactoring that removed the lambda but left the call site intact.

Related errors


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

Appendix: source

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

            if (source == null)
                throw new ArgumentNullException(nameof(source));
            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)

View on GitHub (pinned to 94b5d5ab91)