dotnet/reactive · error · ArgumentNullException

new ArgumentNullException(nameof(comparer))

Error message

new ArgumentNullException(nameof(comparer))

What it means

The three-argument GroupBy(source, keySelector, comparer) throws ArgumentNullException when comparer is null. A non-null IEqualityComparer<TKey> is required to route elements into groups; unlike plain LINQ-to-Objects, this overload does not fall back to EqualityComparer<TKey>.Default. Validation is performed eagerly at call time.

Solutions

  1. Pass a non-null IEqualityComparer<TKey>, e.g. EqualityComparer<TKey>.Default if default semantics are desired.
  2. Fix the comparer factory/registration so it never returns null.
  3. Coalesce: comparer ?? EqualityComparer<TKey>.Default before the call.

Example fix

// before
var groups = source.GroupBy(x => x.Key, resolver.GetComparer<string>()); // may be null
// after
var groups = source.GroupBy(x => x.Key, resolver.GetComparer<string>() ?? EqualityComparer<string>.Default);
Defensive patterns

Strategy: validation

Validate before calling

if (comparer is null) comparer = EqualityComparer<TKey>.Default;

Type guard

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

Try / catch

try { var groups = source.GroupBy(keySelector, comparer); }
catch (ArgumentNullException ex) when (ex.ParamName == "comparer") { log.LogError(ex, "GroupBy comparer was null"); throw; }

Prevention

When it happens

Trigger: Calling GroupBy(source, keySelector, null) — usually a comparer resolved from configuration, DI container, or a lookup that returned null.

Common situations: DI-registered comparers missing from the container; comparer selected conditionally with no default branch; passing a custom comparer class instance that a factory failed to create.

Related errors


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

Appendix: source

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

            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));

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

        public static IAsyncObservable<IGroupedAsyncObservable<TKey, TSource>> GroupBy<TSource, TKey>(this IAsyncObservable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));
            if (comparer == null)
                throw new ArgumentNullException(nameof(comparer));

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

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

            return CreateAsyncObservable<IGroupedAsyncObservable<TKey, TSource>>.From(
                source,

View on GitHub (pinned to 94b5d5ab91)