dotnet/reactive · error · ArgumentNullException
comparer
Error message
comparer
What it means
The comparer overload of GroupByUntil requires an IEqualityComparer<TKey> to group keys; unlike some other overloads there is no default-comparer fallback here, so a null comparer throws ArgumentNullException with parameter name "comparer" (message "Value cannot be null. (Parameter 'comparer')") during eager validation. The exception is thrown synchronously at the call, before any subscription begins.
Solutions
- Pass a valid comparer instance, e.g. `EqualityComparer<TKey>.Default`, or your custom IEqualityComparer<TKey>.
- If you do not need custom key equality, use the two/three-argument overloads of GroupByUntil that omit the comparer so defaults apply.
- Ensure DI/configuration actually provides the comparer; fail at startup if the registration is missing.
- Catch ArgumentNullException at the call site if a missing comparer is an expected, recoverable condition.
Example fix
// before var grouped = source.GroupByUntil(x => x.Key, g => g.TakeUntil(done), null); // after var grouped = source.GroupByUntil(x => x.Key, g => g.TakeUntil(done), EqualityComparer<string>.Default);
Defensive patterns
Strategy: validation
Validate before calling
if (comparer is null)
comparer = EqualityComparer<TKey>.Default; // or throw explicitly Type guard
static bool HasComparer<TKey>(IEqualityComparer<TKey>? c) => c is not null;
Try / catch
try
{
var grouped = source.GroupByUntil(keySelector, durationSelector, comparer);
}
catch (ArgumentNullException ex) when (ex.ParamName == "comparer")
{
// fall back to EqualityComparer<TKey>.Default
} Prevention
- Register IEqualityComparer<TKey> implementations in your DI container and validate at startup.
- Default to EqualityComparer<TKey>.Default when custom key equality is not required.
- Use comparer overloads only when a comparer instance is guaranteed non-null.
- Annotate comparer parameters/fields as non-nullable to surface missing initialization at compile time.
When it happens
Trigger: Calling `source.GroupByUntil(keySelector, durationSelector, null)` where the comparer variable is null — e.g. an injected IEqualityComparer<TKey> not registered in DI, a custom comparer returned null from a factory, or a typo passing null where a comparer instance was intended.
Common situations: DI containers missing an IEqualityComparer<TKey> registration; comparer instances conditionally initialized; refactoring where a default comparer overload call was switched to the comparer overload without supplying an instance.
Related errors
- ArgumentNullException
- ArgumentNullException(nameof(comparer))
- Value cannot be null. (Parameter 'disposable')
- Value cannot be null. (Parameter 'disposable')
- Value cannot be null. (Parameter 'disposable')
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/1a6b0d3e79a56bd7.
Report an issue: GitHub.
Appendix: source
Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupByUntil.cs:184
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, ValueTask<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, ValueTask<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)
throw new ArgumentNullException(nameof(durationSelector));
if (capacity < 0)
throw new ArgumentOutOfRangeException(nameof(capacity));
View on GitHub (pinned to 94b5d5ab91)