dotnet/reactive · error · ArgumentNullException

ArgumentNullException: comparer

Error message

ArgumentNullException: comparer

What it means

GroupByUntil uses an IEqualityComparer<TKey> to route elements into groups. The six-argument overload at GroupByUntil.cs:365 throws ArgumentNullException when comparer is null, since the group map cannot compare keys without one.

Solutions

  1. Pass EqualityComparer<TKey>.Default if no custom comparison is needed
  2. Implement or register a comparer for the key type and ensure the factory cannot return null
  3. Drop the comparer argument to use an overload that defaults to the standard comparer

Example fix

// before
var res = source.GroupByUntil(k => k, g => g.Take(1), 8, comparer /* null */);
// after
var res = source.GroupByUntil(k => k, g => g.Take(1), 8, comparer ?? EqualityComparer<TKey>.Default);
Defensive patterns

Strategy: validation

Validate before calling

if (comparer == null) comparer ??= EqualityComparer<TKey>.Default; // normalize before calling

Type guard

bool HasComparer<TKey>(IEqualityComparer<TKey>? c) => c is not null;

Try / catch

try { var res = source.GroupByUntil(keySelector, durationSelector, capacity, comparer); }
catch (ArgumentNullException ex) when (ex.ParamName == "comparer") { /* retry with EqualityComparer<TKey>.Default */ }

Prevention

When it happens

Trigger: Calling GroupByUntil(observer, subscription, keySelector, durationSelector, capacity, null) — often because a comparer resolved from DI/config was null, or the code intended a comparer-less overload but extra arguments selected this one.

Common situations: Custom key types whose comparer factory returned null; configurably-supplied comparers in generic pipelines; overload confusion after adding capacity/comparer parameters to a previous call.

Related errors


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

Appendix: source

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

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

            return GroupByUntil(observer, subscription, x => new ValueTask<TKey>(keySelector(x)), durationSelector, capacity, comparer);
        }

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

            return GroupByUntil(observer, subscription, keySelector, elementSelector, durationSelector, int.MaxValue, EqualityComparer<TKey>.Default);

View on GitHub (pinned to 94b5d5ab91)