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
- Pass a concrete comparer, e.g. EqualityComparer<TKey>.Default if default hash/equality semantics are acceptable
- Use the GroupByUntil overload without the comparer parameter if default semantics were intended
- 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
- Default comparer parameters to EqualityComparer<TKey>.Default instead of null
- Never forward possibly-null comparer variables straight into Rx operators
- Prefer the comparer-less GroupByUntil overload unless custom equality is required
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
- comparer (Parameter 'comparer')
- nameof(onError)
- nameof(onCompleted)
- nameof(witness)
- threadFactory (Parameter 'threadFactory')
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)