dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'comparer')

Error message

Value cannot be null. (Parameter 'comparer')

What it means

GroupBy's comparer overload requires a non-null IEqualityComparer<TKey> to decide group membership; passing null makes key equality undefined, so the operator throws ArgumentNullException eagerly. Note the default comparer is used automatically when no comparer is supplied.

Solutions

  1. Pass a concrete comparer such as EqualityComparer<TKey>.Default or StringComparer.OrdinalIgnoreCase.
  2. Coalesce before calling: comparer ?? EqualityComparer<TKey>.Default.
  3. Use a GroupBy overload without a comparer to get the default automatically.

Example fix

// before
var grouped = source.GroupBy(x => x.Key, x => x, 16, maybeComparer);
// after
var grouped = source.GroupBy(x => x.Key, x => x, 16, maybeComparer ?? EqualityComparer<TKey>.Default);
Defensive patterns

Strategy: validation

Validate before calling

comparer ??= EqualityComparer<TKey>.Default;
var grouped = source.GroupBy(keySelector, elementSelector, capacity, comparer);

Type guard

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

Try / catch

try
{
    grouped = source.GroupBy(keySelector, elementSelector, capacity, comparer);
}
catch (ArgumentNullException ex) when (ex.ParamName == "comparer")
{
    grouped = source.GroupBy(keySelector, elementSelector); // default comparer
}

Prevention

When it happens

Trigger: Calling AsyncObservable.GroupBy(source, keySelector, elementSelector, capacity, comparer) with comparer == null — e.g. a comparer field/property that was never initialized or a method parameter forwarded as null.

Common situations: DI-resolved or configurable comparer is null; a helper method has a nullable comparer parameter and forwards it directly instead of falling back to EqualityComparer<TKey>.Default.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupBy.cs:262

            return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TElement>>.From(
                source,
                (keySelector, elementSelector, capacity),
                static (source, state, observer) => GroupByCore(source, observer, (o, d) => AsyncObserver.GroupBy(o, d, state.keySelector, state.elementSelector, state.capacity)));
        }

        public static IAsyncObservable<IGroupedAsyncObservable<TKey, TElement>> GroupBy<TSource, TKey, TElement>(this IAsyncObservable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<TSource, ValueTask<TElement>> elementSelector, int capacity, IEqualityComparer<TKey> comparer)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));
            if (elementSelector == null)
                throw new ArgumentNullException(nameof(elementSelector));
            if (capacity < 0)
                throw new ArgumentOutOfRangeException(nameof(capacity));
            if (comparer == null)
                throw new ArgumentNullException(nameof(comparer));

            return CreateAsyncObservable< IGroupedAsyncObservable<TKey, TElement>>.From(
                source,
                (keySelector, elementSelector, capacity, comparer),
                static (source, state, observer) => GroupByCore(source, observer, (o, d) => AsyncObserver.GroupBy(o, d, state.keySelector, state.elementSelector, state.capacity, state.comparer)));
        }

        private static async ValueTask<IAsyncDisposable> GroupByCore<TSource, TKey, TElement>(IAsyncObservable<TSource> source, IAsyncObserver<IGroupedAsyncObservable<TKey, TElement>> observer, Func<IAsyncObserver<IGroupedAsyncObservable<TKey, TElement>>, IAsyncDisposable, (IAsyncObserver<TSource>, IAsyncDisposable)> createObserver)
        {
            var d = new SingleAssignmentAsyncDisposable();

            var (sink, subscription) = createObserver(observer, d);

            var inner = await source.SubscribeSafeAsync(sink).ConfigureAwait(false);
            await d.AssignAsync(inner).ConfigureAwait(false);

            return subscription;
        }

View on GitHub (pinned to 94b5d5ab91)