dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'comparer')

Error message

Value cannot be null. (Parameter 'comparer')

What it means

System.Reactive's GroupByUntil overload with a capacity and comparer validates its arguments eagerly and throws ArgumentNullException when the IEqualityComparer<TKey> comparer is null. Unlike the plain GroupBy operator, this overload requires an explicit comparer; null is not defaulted to EqualityComparer<TKey>.Default, so the call is rejected before any subscription happens.

Solutions

  1. Pass a concrete comparer, e.g. EqualityComparer<TKey>.Default if default hash/equality semantics are acceptable
  2. Use the GroupByUntil overload without the comparer parameter if default semantics were intended
  3. Ensure the comparer factory/DI binding actually resolves a non-null instance before calling GroupByUntil

Example fix

// before
var result = source.GroupByUntil(x => x.Id, x => x, g => Observable.Timer(TimeSpan.FromMinutes(5)), 64, null);
// after
var result = source.GroupByUntil(x => x.Id, x => x, g => Observable.Timer(TimeSpan.FromMinutes(5)), 64, EqualityComparer<int>.Default);
Defensive patterns

Strategy: validation

Validate before calling

if (comparer == null) throw new InvalidOperationException("GroupByUntil requires a non-null comparer; use EqualityComparer<TKey>.Default or the overload without a comparer.");

Type guard

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

Try / catch

try { var q = source.GroupByUntil(ks, es, ds, cap, comparer); }
catch (ArgumentNullException ex) when (ex.ParamName == "comparer") { comparer = EqualityComparer<TKey>.Default; /* retry */ }

Prevention

When it happens

Trigger: Calling Observable.GroupByUntil(source, keySelector, elementSelector, durationSelector, capacity, comparer) with comparer == null (e.g. passing a comparer variable that was never initialized, or a method parameter forwarded through as null).

Common situations: Building a custom key comparison path where the comparer is injected via DI or configuration and the binding silently yields null; refactoring from the comparer-less GroupByUntil overload and passing null explicitly to keep a signature aligned.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Linq/Observable.StandardSequenceOperators.cs:675

            if (elementSelector == null)
            {
                throw new ArgumentNullException(nameof(elementSelector));
            }

            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 s_impl.GroupByUntil(source, keySelector, elementSelector, durationSelector, capacity, comparer);
        }

        /// <summary>
        /// Groups the elements of an observable sequence with the specified initial capacity according to a specified key selector function and selects the resulting elements by using a specified function.
        /// A duration selector function is used to control the lifetime of groups. When a group expires, it receives an OnCompleted notification. When a new element with the same
        /// key value as a reclaimed group occurs, the group will be reborn with a new lifetime request.
        /// </summary>
        /// <typeparam name="TSource">The type of the elements in the source sequence.</typeparam>
        /// <typeparam name="TKey">The type of the grouping key computed for each element in the source sequence.</typeparam>
        /// <typeparam name="TElement">The type of the elements within the groups computed for each element in the source sequence.</typeparam>
        /// <typeparam name="TDuration">The type of the elements in the duration sequences obtained for each group to denote its lifetime.</typeparam>
        /// <param name="source">An observable sequence whose elements to group.</param>
        /// <param name="keySelector">A function to extract the key for each element.</param>
        /// <param name="elementSelector">A function to map each source element to an element in an observable group.</param>
        /// <param name="durationSelector">A function to signal the expiration of a group.</param>

View on GitHub (pinned to 94b5d5ab91)